[英]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.