我使用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);
}
}
}
答案 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
执行时没有错误,但实际上并没有测试任何内容。从技术上讲,它至少会确认notify
在mailService
没有抛出异常时不会抛出异常。我们可以做得更好。
写好考试的关键是问自己,“这种方法应该做什么?”在编写方法之前,您应该能够回答这个问题。对于特定测试,请询问“该输入应该怎么做?”或者“当它依赖于此时应该怎么做?”
在第一种情况下,您创建一个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
将不会抛出异常。如果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
,则编译器会要求它在方法签名中声明它。
答案 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);
}
}