【问题标题】:Not able to unit test the exception thrown in this method using mockito无法使用 mockito 对该方法中引发的异常进行单元测试
【发布时间】:2017-07-11 14:59:28
【问题描述】:

我使用mockito 编写了以下单元测试来测试我的EmailService.java 类。我不确定我是否正确地测试了这个(快乐路径和异常情况)。

此外,我得到了这个Error: 'void' type not allowed here 在我的单元测试中的以下代码sn-p中

when(mockEmailService.notify(anyString())).thenThrow(MailException.class);

我知道,由于我的 notify() 方法返回 void,我得到了那个异常。但不知道如何解决这个问题。我的单元测试或实际课程或两者都需要更改代码吗?

谁能指导一下。

EmailServiceTest.java

public class EmailServiceTest {

    @Rule
    public MockitoJUnitRule rule = new MockitoJUnitRule(this);

    @Mock
    private MailSender mailSender;
    @Mock
    private EmailService mockEmailService;
    private String emailRecipientAddress = "recipient@abc.com";
    private String emailSenderAddress = "sender@abc.com";
    private String messageBody = "Hello Message Body!!!";

    @Test
    public void testNotify() {
        EmailService emailService = new EmailService(mailSender, emailRecipientAddress, emailSenderAddress);
        emailService.notify(messageBody);
    }

    @Test(expected = MailException.class)
    public void testNotifyMailException() {
        when(mockEmailService.notify(anyString())).thenThrow(MailException.class);
        EmailService emailService = new EmailService(mailSender, emailRecipientAddress, emailSenderAddress);
        emailService.notify(messageBody);
    }

}

EmailService.java

public class EmailService {
    private static final Log LOG = LogFactory.getLog(EmailService.class);
    private static final String EMAIL_SUBJECT = ":: Risk Assessment Job Summary Results::";
    private final MailSender mailSender;
    private final String emailRecipientAddress;
    private final String emailSenderAddress;

    public EmailService(MailSender mailSender, String emailRecipientAddress,
            String emailSenderAddress) {
        this.mailSender = mailSender;
        this.emailRecipientAddress = emailRecipientAddress;
        this.emailSenderAddress = emailSenderAddress;
    }

    public void notify(String messageBody) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject(EMAIL_SUBJECT);
        message.setTo(emailRecipientAddress);
        message.setFrom(emailSenderAddress);
        message.setText(messageBody);
        try {
            mailSender.send(message);
        } catch (MailException e) {
            LOG.error("Error while sending notification email: ", e);
        }
    }
}

【问题讨论】:

  • 道歉,复制粘贴问题。修复它
  • 那么你的单元测试失败了吗?您收到什么错误消息?
  • 第一个通过,第二个没有通过。根据 mockito,语法不正确。不确定我的生产代码是否需要返工或单元测试。请指导

标签: java unit-testing junit mockito


【解决方案1】:

不幸的是,问题中给出的代码有很多问题。例如:您指示您的测试抛出异常。并预期该异常会在测试中弥补。

但是:

  try {
        mailSender.send(message);
    } catch (MailException e) {
        LOG.error("Error while sending notification email: ", e);
    }

您的生产代码捕获异常。所以你写了一个只有当你的生产代码不正确时才能通过的测试不正确

现在,如果测试错误:您可以考虑模拟该记录器对象;验证对它的调用是否发生。并且您会将测试用例更改为 not 期望任何异常。这就是 try/catch 的全部意义所在;不是吗。

或者反过来:如果不捕捉就是你想要的;您的单元测试告诉您 try/catch 必须消失。

如前所述,这只是一个问题 - 其他答案可以很好地列出它们。

从这个角度来看,答案可能是:不要试图通过反复试验来学习单元测试。相反:获取一本好书或教程,并学习如何进行单元测试——包括如何正确使用模拟框架。

【讨论】:

    【解决方案2】:

    我将假设这里的EmailService 实现是正确的并专注于测试。他们都有缺陷。虽然testNotify 执行没有错误,但它实际上并没有测试任何东西。从技术上讲,当mailService 没有抛出异常时,它至少会确认notify 不会抛出异常。我们可以做得更好。

    编写好的测试的关键是问问自己,“这个方法应该做什么?”在编写方法之前,您应该能够回答这个问题。对于特定的测试,询问“它应该如何处理这个输入?”或“当它的依赖项这样做时应该怎么做?”

    在第一种情况下,您创建一个MailService 并传递一个MailSender 以及to 和from 地址。调用 notify 方法时,MailService 实例应该做什么?它应该通过send 方法将SimpleMailMessage 传递给MailSender。以下是你的做法(注意,我假设MailSender 实际上采用MailMessage 接口而不是SimpleMailMessage):

    @Mock
    private MailSender mailSender;
    private EmailService emailService;
    private String emailRecipientAddress = "recipient@abc.com";
    private String emailSenderAddress = "sender@abc.com";
    private String messageBody = "Hello Message Body!!!";
    
    @Before
    public void setUp(){
        MockitoAnnotations.initMocks(this);
        emailService = new EmailService(mailSender, emailRecipientAddress, emailSenderAddress);
    }
    
    @Test
    public void testMessageSent() throws MailException {
    
        ArgumentCaptor<MailMessage> argument = ArgumentCaptor.forClass(MailMessage.class);
    
        emailService.notify(messageBody);
    
        Mockito.verify(mailSender).send(argument.capture());
        Assert.assertEquals(emailRecipientAddress, argument.getValue().getTo());
        Assert.assertEquals(emailSenderAddress, argument.getValue().getFrom());
        Assert.assertEquals(messageBody, argument.getValue().getText());
    
    }
    

    这可以确保EmailService 根据传递给其构造函数和notify 方法的参数实际发送您期望的消息。我们不关心MailSender 在这个测试中是否正确地完成了它的工作。我们只是假设它可以工作——大概是因为它已经在其他地方进行了测试,或者是提供的库的一部分。

    对异常的测试更加微妙。由于异常被捕获、记录,然后被忽略,所以没有那么多要测试的东西。我个人不会费心检查是否记录了任何内容。我们真正想做的是确认如果MailSender 抛出MailException 那么notify 不会抛出异常。如果MailExceptionRuntimeException,那么我们应该对此进行测试。基本上,您只需模拟 mailSender 即可引发异常。如果EmailService 没有正确处理它,那么它会抛出一个异常并且测试会失败(这里使用的设置和前面的例子一样):

    @Test
    public void testMailException() throws MailException {
    
        Mockito.doThrow(Mockito.mock(MailException.class)).when(mailSender).send(Mockito.any(MailMessage.class));
        emailService.notify(messageBody);
    
    }
    

    或者,我们可以捕获MailException,然后明确地使测试失败:

    @Test
    public void testMailExceptionAlternate() {
        try {
            Mockito.doThrow(Mockito.mock(MailException.class)).when(mailSender).send(Mockito.any(MailMessage.class));
            emailService.notify(messageBody);
        } catch (MailException ex){
            Assert.fail("MailException was supposed to be caught.");
        }
    }
    

    这两种方法都确认了所需的行为。第二个更清楚的是它正在测试什么。不过,不利的一面是,如果允许 notify 在其他情况下抛出 MailException,那么该测试可能不起作用。

    最后,如果MailException 是一个已检查的异常——那就是它不是RuntimeException——那么你甚至不需要测试它。如果notify 可能会抛出MailException,那么编译器会要求它在方法签名中声明它。

    【讨论】:

    • 谢谢!我认为第二种方法是更好的方法。
    【解决方案3】:

    您不应该真的在嘲笑您正在尝试测试的课程。我认为您在这里真正想做的是模拟 MailSender 以引发异常。我注意到您已经在成功的测试中使用了模拟 MailSender。只需再次使用它并设置期望值:

     when(mailSender.send(any(SimpleMailMessage.class))).thenThrow(MailException.class);
    

    但正如@GhostCat 的回答中提到的,您正在方法中进行异常处理,因此您需要指定一个不同的期望,而不是要抛出的异常。您可以模拟记录器,但模拟静态记录器通常是值得付出更多努力的。您可能需要考虑重新处理异常处理以使其更易于测试。

    【讨论】:

    • 真的吗?这看起来很奇怪,你能粘贴你的新 testNotifyMailException() 方法吗?
    • @Test(expected = MailException.class) public void testNotifyMailException() { when(mailSender.send(any(SimpleMailMessage.class))).thenThrow(MailException.class); mockKlarnaEmailService.notify(messageBody); }
    【解决方案4】:

    1) 请参阅 this 文档了解如何模拟 void 方法异常:

    在你的情况下应该是这样的:

    doThrow(new MailException()).when(mockEmailService).notify( anyString() );
    

    2) 您的testNotify 没有进行适当的测试。调用实际的notify 方法后不会检查预期的结果。

    3) 您的testNotifyMailException 首先模拟notify,然后在非模拟的EmailService 上调用实际的notify 方法。 模拟notify 的全部意义在于测试调用它的代码,而不是您要模拟的实际方法。

    【讨论】:

    • 试过了。获取异常org.springframework.mail.MailException is abstract; cannot be instantiated
    • 尝试使用像MailSendException这样的具体扩展类。
    【解决方案5】:

    根据上述回复,我通过修改我的实际类和单元测试使其工作。

    EmailSendException.java(添加新类以提高可测试性)

    public class EmailSendException extends RuntimeException {
        private static final long serialVersionUID = 1L;
    
        public EmailSendException(String message) {
            super(message);
        }
    
        public EmailSendException(String message, Throwable cause) {
            super(message, cause);
        }
    }
    

    EmailService.java(不是捕获,而是抛出 RuntimeException)

    public class EmailService {
        private static final Log LOG = LogFactory.getLog(EmailService.class);
        private static final String EMAIL_SUBJECT = ":: Risk Assessment Job Summary Results::";
        private final MailSender mailSender;
        private final String emailRecipientAddress;
        private final String emailSenderAddress;
        private static final String ERROR_MSG = "Error while sending notification email";
    
        public EmailService(MailSender mailSender, String emailRecipientAddress,
                                  String emailSenderAddress) {
            this.mailSender = mailSender;
            this.emailRecipientAddress = emailRecipientAddress;
            this.emailSenderAddress = emailSenderAddress;
        }
    
        public void notify(String messageBody) {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setSubject(EMAIL_SUBJECT);
            message.setTo(emailRecipientAddress);
            message.setFrom(emailSenderAddress);
            message.setText(messageBody);
            try {
                mailSender.send(message);
            } catch (MailException e) {
                throw new EmailSendException(ERROR_MSG, e);
            }
        }
    }
    

    EmailServiceTest.java(模拟和测试)

    public class EmailServiceTest {
    
        @Rule
        public MockitoJUnitRule rule = new MockitoJUnitRule(this);
    
        @Mock
        private MailSender mailSender;
        private String emailRecipientAddress = "recipient@abc.com";
        private String emailSenderAddress = "sender@abc.com";
        private String messageBody = "Hello Message Body!!!";
        @Mock
        private EmailService mockEmailService;
    
        @Test
        public void testNotify() {
            EmailService EmailService = new EmailService(mailSender, emailRecipientAddress, emailSenderAddress);
            EmailService.notify(messageBody);
        }
    
        @Test(expected = KlarnaEmailSendException.class)
        public void testNotifyMailException() {
            doThrow(new KlarnaEmailSendException("Some error message")).when(mockKlarnaEmailService).notify(anyString());
            mockKlarnaEmailService.notify(messageBody);
        }
    }
    

    【讨论】:

    • mockKlarnaEmailService 无处引用。此外,您应该遵循 Plog 的建议。您应该模拟在您的测试类中调用的依赖项:MailSender.send().
    猜你喜欢
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    • 2014-11-20
    • 2018-09-19
    • 2023-03-06
    • 1970-01-01
    • 2018-01-03
    • 1970-01-01
    相关资源
    最近更新 更多