Java 模拟引发异常的服务?
我想测试一个JSF支持Bean方法“isInProgress”,该方法委托给服务方法“isInProgress”。当服务方法抛出异常时,bean应该在特定的事件记录器上放置一个事件并返回false 当我调试下面的测试时,我进入catch块。模拟服务不会引发异常,但会返回一个“默认答案”,该答案对于th boolean为false我做错了什么?Java 模拟引发异常的服务?,java,unit-testing,junit,mockito,Java,Unit Testing,Junit,Mockito,我想测试一个JSF支持Bean方法“isInProgress”,该方法委托给服务方法“isInProgress”。当服务方法抛出异常时,bean应该在特定的事件记录器上放置一个事件并返回false 当我调试下面的测试时,我进入catch块。模拟服务不会引发异常,但会返回一个“默认答案”,该答案对于th boolean为false我做错了什么? @Test public void testIsInProgressExeption() { //prepare object and input
@Test
public void testIsInProgressExeption() {
//prepare object and inputs
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
try {
when(bean.getService().isInProgress()).thenThrow(new Exception());
} catch (Exception e) {
//prepare expected object and result
MyBean expectedBean = new MyBean();
expectedBean.setService(service);
boolean expected = false;
//execute method under test
boolean actual = bean.isInProgress();
//check return values and exceptions
assertEquals(expected, actual);
//check that bean did not change unexpectedly
assertTrue(bean.equals(expectedBean));
//check sideeffects on event log
assertTrue(logEvents.containsMessage("MDI09"));
}
}
我还想知道是否可以以某种方式避免“when”调用的try-catch,因为实际的异常被测试的bean吞没了。事实上,我认为“声明式”将方法的名称传递给“when”就足够了有没有办法弄到那种清洁剂?
@Test
public void testIsInProgressExeption() {
//prepare object and inputs
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
try {
when(bean.getService().isInProgress()).thenThrow(new Exception());
} catch (Exception e) {
//prepare expected object and result
MyBean expectedBean = new MyBean();
expectedBean.setService(service);
boolean expected = false;
//execute method under test
boolean actual = bean.isInProgress();
//check return values and exceptions
assertEquals(expected, actual);
//check that bean did not change unexpectedly
assertTrue(bean.equals(expectedBean));
//check sideeffects on event log
assertTrue(logEvents.containsMessage("MDI09"));
}
}
以下是更新后的测试,以供参考:
@Test
public void testIsInProgressExeption() throws Exception {
//prepare object and inputs
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
when(bean.getService().isInProgress()).thenThrow(new Exception());
//prepare expected object and result
MyBean expectedBean = new MyBean();
expectedBean.setService(service);
boolean expected = false;
//execute method under test
boolean actual = bean.isInProgress();
//check return values and exceptions
assertEquals(expected, actual);
//check that bean did not change unexpectedly
assertTrue(bean.equals(expectedBean));
//check sideeffects on event log
assertTrue(logEvents.containsMessage("MDI09"));
}
将when子句移出try块,并将其更改为:
when(service.isInProgress()).thenThrow(new Exception());
现在,它应该在调用时抛出异常。您做错了。首先,您应该使用BDD或AAA关键字和BDD来安排您的测试:
@Test public void testIsInProgressExeption() {
// given
// when
// then
}
在给定的部分中,您将编写夹具,即测试场景的设置。在when部分中,您将调用生产代码,即测试对象。最后,在when部分,您将编写验证和/或断言
存根在夹具中,所以这条线放错了位置,它不属于这里,它只是行为的定义
when(bean.getService().isInProgress()).thenThrow(new Exception());
但是,您应该直接使用服务引用,而不是bean.getService()
,这是akward
我真的不明白为什么要在catch子句中创建bean的新实例,这很奇怪。但下面是我写测试的方法。请注意,我在单元测试名称中解释了测试实际测试的行为,用驼峰大小写是一种令人痛苦的阅读方式,因此我使用下划线的约定,在测试中是可以的
@Test public void when_service_throw_Exception_InProgress_then_returns_false() throws Exception {
// given
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
when(service.isInProgress()).thenThrow(new Exception());
// when
boolean result = bean.isInProgress();
// then
assertFalse(result);
}
此外,我将拆分事件的断言,这是一种不同的行为:
@Test public void when_service_throw_Exception_InProgress_then_log_event_MDI09() throws Exception {
// given
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
// somehow set up the logEvents collaborator
when(service.isInProgress()).thenThrow(new Exception());
// when
bean.isInProgress();
// then
assertTrue(logEvents.containsMessage("MDI09"));
}
您甚至可以进一步简化夹具,如果使用JUnit,您可以编写以下代码:
@RunWith(MockitoJUnitRunner.class)
public class MyBeanTest {
@Mock MyService service;
@Mock LogEvents logEvents;
@InjectMocks MyBean bean;
@Test public void when_service_throw_Exception_InProgress_then_log_event_MDI09() throws Exception {
// given
when(service.isInProgress()).thenThrow(Exception.class);
// when
bean.isInProgress();
// then
verify(logEvents).logEvent("MDI09");
}
}
在上面的示例中,我还推断了日志事件的内容,但这只是为了给出可能的想法。对于记录,我正在进行状态基测试。有趣的是,Fowler在一篇非常好的文章中发表了一篇非常好的文章,这篇文章的思路与模拟和基于交互的测试完全相同,但是service.isInProgress()抛出了一个异常,因此编译器强迫我处理它!啊,在测试decl中添加一个抛出就可以了。在你的测试方法中添加一个例外签名我不太了解测试框架,但正如你在评论中看到的,我的方法是非常系统的,事实上我不知道有任何更好的方法(而且它使用简单的方法!)。我不仅根据返回值测试输入样本。我还测试了被测试对象的状态变化(在本例中不应发生这种情况),以及其他对象中可能出现的副作用。此外,你的语气很刺耳。如果语气显得刺耳或刺耳,我很抱歉,这不是我的本意。无论如何,主要的想法是提供一种渐进的方式来进行特定的、集中的测试,这样做可以潜在地推动代码进行更好的设计。在测试中评论用户正在做什么的简单事实是一种气味。另外,测试的真正意图并没有在
testIsInProgressException
中表达出来,也就是说,测试bean中没有状态副作用,为此编写一个测试是值得的。练习TDD可以帮助解决这一问题。这是一本很棒的书,里面有很多有价值的信息。这个博客来自詹姆斯·卡尔的《公共资源清单》。当然还有很多其他有趣的阅读。