[英]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
不会抛出异常。 如果MailException
是RuntimeException
那么我们应该测试它。 基本上,你只是模拟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.