繁体   English   中英

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

[英]Not able to unit test the exception thrown in this method using mockito

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

此外,我收到此Error: 'void' type not allowed here我的单元测试中的以下代码段中Error: 'void' type not allowed here

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

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

但:

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

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

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

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

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

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

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

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

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

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

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

在第一种情况下,您创建一个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它的工作。 我们只是假设它有效 - 大概是因为它要么在别处测试,要么在提供的库的一部分。

对例外的测试更加微妙。 由于异常被捕获,记录,然后被忽略,因此没有那么多的测试。 我个人不打算检查是否有任何记录。 我们真正想要做的是确认如果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那么编译器会要求它在方法签名中声明它。

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

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

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

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

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

基于上面提到的响应,我通过修改我的实际类和单元测试来实现它。

EmailSendException.java(为了提升testablity而添加的新类)

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM