How can I partially mock a method that throws exceptions with Mockito?

It is useful to check exception handling. In this particular case, I have an extractor that will perform a specific task when an exception is thrown while parallelizing a specific class.

Sample code

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

      

Things i have tried

Here's a list of the things I've tried. For brevity, I have not filled in all the mundane details.

Espionage

The following method does nothing and does not throw an exception. 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));
}

      

Mocking

The following did not work because partial layouts 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));
}

      

ThenAnswer

This works, but I'm not sure if it's the right way to do it:

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

      

Question

While the method thenAnswer

works, it doesn't seem to be the correct solution. What is the correct way to do partial layout for this situation?

+3


source to share


1 answer


I don't quite understand what you are with mockery and espionage, but you really only need to mock.

Firstly, I ran into several snags trying to mock some reason. I believe it has to do with a call spy

that got messed up in some way. I eventually overcame them, but I wanted something simple.

Then I noticed something with the way you spied (the basis of my approach):

MyHelper spied = spy(fixture.adapter);

      

This means that you want the instance to be MyHelper

ridiculed, not the spy. The worst part is that even if this object is fully hydrated, it will not be entered correctly as you did not reassign it to the test object (which I assume is equal fixture

).

My preference is to use MockitoJUnitRunner

to help with injection of mocking instances, and from there I build the foundation of what I really need to mock.

There's only one mocked instance and then a test object, and this declaration ensures that both were instantiated and injected:

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {

    @Mock
    private MyHelper adapter;

    @InjectMocks
    private MyService fixture;
}

      



The idea is that you enter your breadboard into the instrument. You don't have to use this - you can use standard setters in your declaration @Before

, but I prefer this as it greatly reduces the boilerplate code you have to write to make mock work.

Now only one change needs to be made: delete the spy instance and replace its previous use with the actual layout.

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

      

When all the code is hoisted, this will go through:

@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 wanting to leave the question / use case incomplete, I circled around and replaced the test with inner classes and it works great:

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

      

+1


source







All Articles