模拟抛出异常的服务?

3
我想测试一个JSF Backing-Bean方法“isInProgress”,该方法委托给一个服务方法“isInProgress”。当服务方法抛出异常时,bean应该将事件放在特定的事件记录器上并返回false。
当我调试以下测试时,我进入了catch块。模拟的服务没有抛出异常,而是返回一个布尔值为false的“默认答案”。我做错了什么?
我还想知道是否可以以某种方式避免“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"));

}
3个回答

3
将when子句移出try块并改为:
when(service.isInProgress()).thenThrow(new Exception());

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

但是 service.isInProgress() 抛出了一个异常,所以编译器强制我处理它! - Bastl
将“throws”添加到“test-decl”中即可。 - Bastl
1
将异常添加到您的测试方法签名中。 - Stig Hausberg

0

-1

你做错了。首先,你应该用BDDAAA关键字来布置你的测试,使用BDD:

@Test public void testIsInProgressExeption() {
    // given

    // when

    // then

}

given部分,您将编写fixture,即测试场景的设置。在when部分,您将调用生产代码,即被测试的主题。最后,在then部分,您将编写验证和/或断言。
存根放在fixture中,因此此行放错了位置,它不属于这里,它只是行为的定义。
when(bean.getService().isInProgress()).thenThrow(new Exception());

然而,你应该直接使用服务引用而不是bean.getService(),这很奇怪。

我真的不明白为什么你在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,甚至可以进一步简化fixture,你可以编写以下代码:

@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");
    }
}

在上面的例子中,我还推断了日志事件的内容,但这只是为了让大家了解可能性。

1
我对测试框架不是很了解,但正如您在评论中所看到的,我的方法非常系统化,实际上我不知道有什么更好的方法(而且它使用简单的手段!)。 我不仅测试输入样本与返回值,还测试被测试对象的状态变化(在这种情况下不应发生),以及其他对象可能产生的副作用。此外,您的语气相当激烈。 - Bastl
如果语气显得严厉或过于激进,我很抱歉,这并不是我的本意。无论如何,主要想法是提供一种渐进的方式来进行特定和聚焦的测试,通过这样做,您可以潜在地将代码驱动到更好的设计中。仅仅是在测试中注释正在执行的内容就是一种“异味”。此外,您的测试的真正意图没有在testIsInProgressException中表达出来,即测试您的bean中是否存在状态副作用是值得编写测试的。 - bric3
实践TDD可以帮助解决这个问题。这本书《Growing Object Oriented Software Guided by Tests》提供了许多有价值的信息。James Carr的博客列出了一些常见的测试反模式。当然还有许多其他有趣的阅读材料。 - bric3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接