如何使用Mockito部分模拟一种抛出异常的方法



测试异常处理很有用。在这种特定情况下,我有一个提取器,该提取器将在抛弃特定类的同时抛出异常时执行特定任务。

示例代码

下面是代码的简化示例。生产版本要复杂得多。

public class Example {
    public static enum EntryType {
        TYPE_1,
        TYPE_2
    }
    public static class Thing {
        List<String> data = new ArrayList<String>();
        EnumSet<EntryType> failedConversions = EnumSet.noneOf(EntryType.class);
    }
    public static class MyHelper {
        public String unmarshal(String input) throws UnmarshalException {
            // pretend this does more complicated stuff
            return input + " foo "; 
        }
    }
    public static class MyService {
        MyHelper adapter = new MyHelper();
        public Thing process() {
            Thing processed = new Thing();
            try {
                adapter.unmarshal("Type 1");
            } catch (UnmarshalException e) {
                processed.failedConversions.add(EntryType.TYPE_1);
            }
            // do some stuff
            try {
                adapter.unmarshal("Type 2");
            } catch (UnmarshalException e) {
                processed.failedConversions.add(EntryType.TYPE_2);
            }
            return processed;
        }
    }
}

我尝试过的东西

这是我尝试过的事情的列表。简而言之,我还没有填写所有平凡的细节。

间谍

以下方法无能为力,异常不会投掷。我不确定为什么。

@Test
public void shouldFlagFailedConversionUsingSpy()
        throws Exception {
    MyHelper spied = spy(fixture.adapter);
    doThrow(new UnmarshalException("foo")).when(spied).unmarshal(
            Mockito.eq("Type 1"));
    Thing actual = fixture.process();
    assertEquals(1, actual.failedConversions.size());
    assertThat(actual.failedConversions.contains(EntryType.TYPE_1), is(true));
}

嘲笑

以下内容无效,因为部分模拟似乎与抛出异常的方法的表现不佳。

@Test
public void shouldFlagFailedConversionUsingMocks()
        throws Exception {
    MyHelper mockAdapter = mock(MyHelper.class);
    when(mockAdapter.unmarshal(Mockito.anyString())).thenCallRealMethod();
    when(mockAdapter.unmarshal(Mockito.eq("Type 2"))).thenThrow(
            new UnmarshalException("foo"));
    Thing actual = fixture.process();
    assertEquals(1, actual.failedConversions.size());
    assertThat(actual.failedConversions.contains(EntryType.TYPE_2), is(true));
}

thenanswer

这有效,但我不确定这是这样做的正确方法:

@Test
public void shouldFlagFailedConversionUsingThenAnswer() throws Exception {
    final MyHelper realAdapter = new MyHelper();
    MyHelper mockAdapter = mock(MyHelper.class);
    fixture.adapter = mockAdapter;
    when(mockAdapter.unmarshal(Mockito.anyString())).then(
            new Answer<String>() {
                @Override
                public String answer(InvocationOnMock invocation)
                        throws Throwable {
                    Object[] args = invocation.getArguments();
                    String input = (String) args[0];
                    if (input.equals("Type 1")) {
                        throw new UnmarshalException("foo");
                    }
                    return realAdapter.unmarshal(input);
                }
            });
    Thing actual = fixture.process();
    assertEquals(1, actual.failedConversions.size());
    assertThat(actual.failedConversions.contains(EntryType.TYPE_1), is(true));
}

问题

尽管thenAnswer方法有效,但似乎并不是正确的解决方案。在这种情况下执行部分模拟的正确方法是什么?

我不是确定您对嘲笑和间谍的了解,但是您只需要在这里嘲笑。

首先,出于任何原因尝试模拟时,我遇到了一些障碍。我相信这与以某种方式混乱的spy呼叫有关。我最终确实克服了这些,但我想得到一些简单的通过。

接下来,我确实注意到了您的间谍方式(我的方法的基础):

MyHelper spied = spy(fixture.adapter);

这意味着您需要嘲笑MyHelper的实例, spied。最糟糕的部分是,即使该对象完全水合,它也无法正确注入,因为您还没有将其重新分配给测试对象(我认为这是fixture)。

我的偏爱是使用MockitoJUnitRunner来帮助注射模拟实例,从那里我建立了我实际上需要模拟的基础。

只有一个模拟的实例,然后是测试对象,此声明将确保它们既实例化和注入:

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
    @Mock
    private MyHelper adapter;
    @InjectMocks
    private MyService fixture;
}

想法是,您将模拟注入固定装置。您不必使用此功能 - 您可以在@Before声明中使用标准设定器,但是我更喜欢这一点,因为它大大降低了您必须编写的样板代码才能嘲笑。

现在只有一个更改要做:删除间谍实例,然后用实际模拟替换其以前的用法。

doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));

随着所有代码的提升,这通过了:

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
    @Mock
    private MyHelper adapter;
    @InjectMocks
    private MyService fixture;
    @Test
    public void shouldFlagFailedConversionUsingSpy()
            throws Exception {
        doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));
        Thing actual = fixture.process();
        assertEquals(1, actual.failedConversions.size());
        assertThat(actual.failedConversions.contains(Example.EntryType.TYPE_1), is(true));
    }
}

不是要留下问题/用例不完整的人,我盘旋并用内部类代替了测试,它也可以正常工作:

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
    @Mock
    private Example.MyHelper adapter;
    @InjectMocks
    private Example.MyService fixture;
    @Test
    public void shouldFlagFailedConversionUsingSpy()
            throws Exception {
        doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));
        Example.Thing actual = fixture.process();
        assertEquals(1, actual.failedConversions.size());
        assertThat(actual.failedConversions.contains(Example.EntryType.TYPE_1), is(true));
    }
}

相关内容

  • 没有找到相关文章