Java 模拟引发异常的服务?

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

我想测试一个JSF支持Bean方法“isInProgress”,该方法委托给服务方法“isInProgress”。当服务方法抛出异常时,bean应该在特定的事件记录器上放置一个事件并返回false

当我调试下面的测试时,我进入catch块。模拟服务不会引发异常,但会返回一个“默认答案”,该答案对于th boolean为false我做错了什么?

@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());

现在,它应该在调用时抛出异常。

您做错了。首先,您应该使用BDDAAA关键字和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可以帮助解决这一问题。这是一本很棒的书,里面有很多有价值的信息。这个博客来自詹姆斯·卡尔的《公共资源清单》。当然还有很多其他有趣的阅读。