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

Java 如何使用Mockito部分模拟抛出异常的方法?,java,unit-testing,mockito,partial-mocks,Java,Unit Testing,Mockito,Partial Mocks,测试异常处理非常有用。在这个特定的例子中,我有一个提取器,它将在对特定类进行解组时引发异常时执行特定的任务 示例代码 下面是代码的简化示例。生产版本要复杂得多 public class Example { public static enum EntryType { TYPE_1, TYPE_2 } public static class Thing { List<String> data = new Arra

测试异常处理非常有用。在这个特定的例子中,我有一个提取器,它将在对特定类进行解组时引发异常时执行特定的任务

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

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 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));
}
然后回答 这是可行的,但我不确定这样做是否正确:

@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));
}
@测试
public void应标记FailedConversionSingThenAnswer()引发异常{
最终MyHelper realAdapter=新的MyHelper();
MyHelper mockAdapter=mock(MyHelper.class);
fixture.adapter=mockAdapter;
当(mockAdapter.unmarshal(Mockito.anyString())。然后(
新答案(){
@凌驾
公共字符串应答(调用锁调用)
扔掉的{
对象[]args=invocation.getArguments();
字符串输入=(字符串)参数[0];
if(输入等于(“类型1”)){
抛出新的解组异常(“foo”);
}
返回realAdapter.unmarshal(输入);
}
});
实际情况=fixture.process();
assertEquals(1,实际.failedConversions.size());
断言(实际.failedConversions.contains(EntryType.TYPE_1)为(true));
}
问题: 虽然
thenAnswer
方法有效,但它似乎不是正确的解决方案。在这种情况下,进行部分模拟的正确方法是什么?

我不太确定你的模拟和间谍活动是什么意思,但你只需要在这里进行模拟

首先,无论出于什么原因,我在尝试你的模拟时遇到了一些障碍。我相信这与间谍的电话有关,电话在某种程度上搞砸了。我最终克服了这些困难,但我想做一些简单的事情

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

这意味着您需要模拟而不是监视
MyHelper
的实例。最糟糕的是,即使该对象完全水合,也无法正确注入,因为您没有将其重新分配给测试对象(我认为是
夹具

我倾向于使用
MockitoJUnitRunner
来帮助注入模拟实例,并从中建立我实际需要模拟的基础

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

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {

    @Mock
    private MyHelper adapter;

    @InjectMocks
    private MyService fixture;
}
这个想法是你把你的模拟注射到夹具中。您不必使用它-您可以在
@之前的
@声明中使用标准制定者,但我更喜欢使用它,因为它大大减少了您必须编写的样板代码,以使模拟工作正常进行

现在只需要做一个更改:删除spy实例并用实际的mock替换它以前的用法

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

我担心的是你把一个被嘲笑的元帅和一个新的元帅混在一起了。你能告诉我们你是如何尝试测试间谍和模拟的吗?我的直觉告诉我,您可能只需要模拟,但我希望看到完整性的另一种方法。@Makoto我已经用精简的测试版本更新了这个问题,它再现了我在生产代码中看到的问题。希望这能更好地解释这种情况。谢谢你抽出时间。我有一个问题,我想我最初是这样设置的,因为我将处理后的值分配给Thing实例。我的同事说问题是由在同一测试中测试两个东西引起的。我认为模仿适配器是正确的方法。再次感谢你的帮助!
@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));
    }
}