如何使用 Mockito 部分模拟抛出异常的方法?
How to partial mock a method that throws exceptions using Mockito?
测试异常处理很有用。在这种特定情况下,我有一个提取器,它会在解组特定 class.
时抛出异常时执行特定任务
示例代码
下面是代码的简化示例。生产版本要复杂得多。
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));
}
然后回答
这可行,但我不确定这样做是否正确:
@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
的实例被模拟出来, 而不是 被窥探。最糟糕的是,即使这个对象完全水合,它也不会被正确注入,因为你没有将它重新分配给测试对象(我假设是 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));
}
}
不想让 question/use 案例不完整,我绕了一圈,用内部 类 替换了测试,它也工作正常:
@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));
}
}
测试异常处理很有用。在这种特定情况下,我有一个提取器,它会在解组特定 class.
时抛出异常时执行特定任务示例代码
下面是代码的简化示例。生产版本要复杂得多。
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));
}
然后回答
这可行,但我不确定这样做是否正确:
@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
的实例被模拟出来, 而不是 被窥探。最糟糕的是,即使这个对象完全水合,它也不会被正确注入,因为你没有将它重新分配给测试对象(我假设是 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));
}
}
不想让 question/use 案例不完整,我绕了一圈,用内部 类 替换了测试,它也工作正常:
@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));
}
}