无法使用mockito对此方法中抛出的异常进行单元测试

时间:2017-07-11 14:59:29

标签: java unit-testing junit mockito

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

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

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

我理解,因为我的notify()方法返回无效,我得到了那个例外。但不知道如何解决这个问题。我的单元测试或实际课程或两者都需要更改代码吗?

请有人指导。

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);
        }
    }
}

5 个答案:

答案 0 :(得分:3)

不幸的是,问题中给出的代码有很多问题。例如:您指示测试抛出异常。并且期望该异常进入测试。

但是:

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

您的生产代码捕获异常。所以你编写的测试只能在你的生产代码不正确时才能通过

现在,如果测试错误:可以查看模拟该记录器对象;验证是否发生了对它的调用。并且您将测试用例更改为期望任何异常。这就是try / catch的重点;不是。

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

如上所述,这只是一个问题 - 其他答案在列出它们方面做得很好。

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

答案 1 :(得分:2)

你真的不应该嘲笑你想要测试的一个类。我认为你真正想要做的就是模仿MailSender抛出异常。我注意到你在成功测试中已经使用了模拟MailSender。再次使用它并设定期望:

{{1}}

但正如在@ GhostCat的回答中提到的那样,你在方法中进行异常处理,所以除了要抛出的异常之外,你需要指定一个不同的期望。你可以模拟记录器,但是模拟静态记录器通常需要付出很多努力。您可能需要考虑重新处理异常处理,以便更容易测试。

答案 2 :(得分:2)

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

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

在第一种情况下,您创建一个MailService传递MailSender以及来往地址。调用MailService方法时,notify实例应该执行什么操作?它应该通过SimpleMailMessage方法将MailSender传递给send。这是你如何做到的(注意,我假设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 :(得分:1)

1)请参阅this doc,了解如何使用例外模拟void方法:

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

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

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

3)您的testNotifyMailException首先嘲笑notify,然后在非模拟的EmailService上调用实际的notify方法。 模拟notify的重点是测试调用它的代码,而不是您正在嘲笑的实际方法。

答案 4 :(得分:0)

基于上述回答,我通过修改我的实际课程和单元测试来实现它。

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);
    }
}