It's useful to test exception handling. In this specific case, I have a extractor that will do a specific task when an exception is thrown while unmarshaling a specific class.
Below is a simplified example of the code. The production version is much more complicated.
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;
}
}
}
Here's a list of things I've tried. For brevity, I haven't filled in all the mundane details.
The following method doesn't do anything and the exception doesn't throw. I'm not sure why.
@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));
}
The following didn't work because partial mocks don't seem to play well with methods that throw exceptions.
@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));
}
This works, but I'm not sure if it's the proper way to do this:
@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));
}
Although the thenAnswer
method works, it doesn't seem to be the proper solution. What is the correct way to perform a partial mock for this situation?
I'm not quite sure what you were getting at with the mocking and the spying, but you only really need to mock here.
First, I ran into a few snags when trying your mocks out for whatever reason. I believe this had to do with the spy
call which was messed up in some way. I did eventually overcome these, but I wanted to get something simple to pass.
Next, I did notice something off with the way you were spying (the basis of my approach):
MyHelper spied = spy(fixture.adapter);
This implies that you want an instance of MyHelper
mocked out, not spied. The worst part is that even if this object were fully hydrated, it wouldn't be properly injected since you haven't reassigned it to the test object (which I presume is fixture
).
My preference is to use the MockitoJUnitRunner
to help with the injection of mocked instances, and from there I build up a basis of what it is I actually need to mock.
There's only one mocked instance and then the test object, and this declaration will ensure that they're both instantiated and injected:
@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {
@Mock
private MyHelper adapter;
@InjectMocks
private MyService fixture;
}
The idea is that you're injecting your mock into the fixture. You don't have to use this - you could use standard setters in a @Before
declaration, but I prefer this since it greatly reduces the boilerplate code you have to write to get mocking to work.
Now there's only one change to be made: remove the spy instance and replace its previous usage with the actual mock.
doThrow(new UnmarshalException("foo")).when(adapter).unmarshal(eq("Type 1"));
With all of the code hoisted, this passes:
@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));
}
}
Not being one to want to leave the question/use case incomplete, I circled around and replaced the test with the inner classes, and it works fine too:
@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));
}
}