2

I am having some difficulty mocking out the necessary dependencies for some unit tests. I suspect I am not correctly understanding how to use the Mockito when().thenReturn().

Here is (hopefully the relevant) part of the code for the service I want to test:

@Singleton
public class AppointmentService {
    private DataConnector connector;

    @Inject
    public AppointmentService(DataConnector connector) {
        this.connector = connector;
    }

    public Appointment getAppointment(UriInfo uriInfo, String id) {
        HttpResponse response = connector.getAppointment(id);
        return validateResponse(response, uriInfo);
    }

    protected Appointment validateResponse(HttpResponse response, UriInfo uriInfo) {
        try {
            AppointmentWrapper appointmentWrapper = JacksonJsonUtility.readValue(response.getInputStream(), AppointmentWrapper.class);
            if(appointmentWrapper.getAppointment()!=null) {
                return appointmentConverterHelper(appointmentWrapper.getAppointment(), uriInfo.getPath());
            }
        } catch(IOException e) {
            throw new ResponseParsingException("IOException: " + e.getMessage());
        }
    }

    protected Appointment appointmentConverterHelper(ExternalAppointmentModel appointmentDto, String uriPath) {
        if (appointmentDto == null) {
            throw new BrickworkQueryException("Got an invalid appointment object from Brickwork!");
        }

        Customer customer = new Customer(new Name(appointmentDto.getCustomer().getName()),
            appointmentDto.getCustomer().getEmail(),
            String.valueOf(appointmentDto.getCustomer().getCode()));

        return new Appointment(
            appointmentDto.getId(),
            customer);
    }

}

My understanding is that my unit test for this method should look something like this:

public class AppointmentServiceTest {

    private UriInfo mockUriInfo;
    private HttpResponse mockHttpResponse;

    @Mock
    private DataConnector mockDataConnector;

    @InjectMocks
    private AppointmentService appointmentService;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mockUriInfo();
        mockHttpResponse();
    }

    private void mockUriInfo() {
        mockUriInfo = mock(UriInfo.class);
        when(mockUriInfo.getRequestUri()).thenReturn(
            URI.create("http://localhost:8080"));
    }

    private void mockHttpResponse() {
        mockHttpResponse = mock(HttpResponse.class);
        when(mockHttpResponse.getInputStream()).thenReturn(
            IOUtils.toInputStream("{\"appointment\":{}}"));
    }

    @Test
    public void testGetAppointment() throws Exception {
        when(mockDataConnector.getAppointmentById(Mockito.anyString())).
            thenReturn(mockHttpResponse);
        when(appointmentService.validateResponse(Mockito.any(HttpResponse.class), Mockito.any(UriInfo.class))).
            thenReturn(new Appointment());
        appointmentService.getAppointment(mockUriInfo, "12345");
        verify(mockDataConnector).getAppointment(Mockito.anyString());
    }
}

When I run my test I get a NullPointerException in the appointmentConverterHelper method when trying to call appointmentDto.getCustomer(), which is understandably empty. As the code is running now, it seems to me that I will need to return an InputStream that contains a fully valid Appointment in my mockHttpResponse. Since I have when(appointmentService.validateResponse(mockHttpResponse, mockUriInfo)).thenReturn(new Appointment()); shouldn't that pick up the validateResponse call and return the empty Appointment object rather than actually running validateResponse?

For reference, here is the stack trace on the Exception - line 140 of AppointmentServiceTest is the when statement above. Line 176 of the AppointmentsService is the JacksonJsonUtility.readValue(response.getInputStream(), AppointmentWrapper.class); line at the very beginning of the validateResponse method:

java.lang.NullPointerException
    at com.nike.appointmentservice.service.AppointmentsService.validatedResponse(AppointmentsService.java:176)
    at com.nike.appointmentservice.service.AppointmentsServiceTest.testGetAppointmentById(AppointmentsServiceTest.java:140)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)```
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
DanHam
  • 340
  • 2
  • 17

2 Answers2

3

I think the issue might be that the when statement is trying to match against a specific instance and is failing. To get started with Mockito unit tests you should replace the specific values in your when statements with Mockito placeholders that represent "anything". For example:

when(mockDataConnector.getAppointmentById(Mockito.anyString())).
  thenReturn(mockHttpResponse);

when(appointmentService.validateResponse(Mockito.any(HttpResponse.class), Mockito.any(UriInfo.class))).
        thenReturn(new Appointment());

When you have the basic unit tests working you can then add in more specific unit tests.

Update

I now notice that you are trying to test the AppointmentService class but you are also mocking out the validateResponse method within that class. I'm not enough of a Mockito expert to know if this is completely incorrect, but this is not the way that I use Mockito in my unit tests. The class I am testing is not mocked at all, but instead I mock out classes that it calls so as to simulate variable behaviour and see how the tested class responds.

I think you might need to mock the JacksonJsonUtility class (powermock helps with static classes) and remove the mock on the validateResponse method. You will need to construct the mock response to the JacksonJsonUtility.readValue method such that it contains everything that the AppointmentService class expects.

jwaddell
  • 1,892
  • 1
  • 23
  • 32
  • Made that edit, but looks like I'm still getting that NullPointerException inside the validateResponse method. Do you think that means I'm still trying to match against a specific instance somehow? – DanHam Aug 23 '16 at 06:46
  • Looks like the exception is getting thrown on the validateResponse `when` statement even when I comment out the actual `appointmentService.getAppointment` call. So I think the problem is with the `when` statement I have trying to run the `validateResponse` method itself rather than just return the mocked response? – DanHam Aug 23 '16 at 14:45
  • This is a pretty basic unit test fundamentals question, but wouldn't I want to mock out the `validateResponse` method to ensure I am testing only the `getAppointment` method? Or is it valid to include multiple methods in the same unit test as long as they are in the same class? – DanHam Aug 24 '16 at 00:36
  • I usually consider the "unit" to be a class - I will have a corresponding unit test class for each class to be tested. I use a code coverage tool (e.g. EMMA) to ensure that the entire class is "exercised" by the unit tests. It might depend on the particular project as to what constitutes a "unit" in that project - see http://stackoverflow.com/documentation/unit-testing/570/introduction-to-unit-testing#t=201608240054306844858&a=remarks – jwaddell Aug 24 '16 at 01:02
0

Based on this post, I think spying the AppointmentService is the way to go to allow me to mock out the validateResponse call. So here's how my test looks now:

public class AppointmentServiceTest {

    private UriInfo mockUriInfo;
    private HttpResponse mockHttpResponse;

    @Mock
    private DataConnector mockDataConnector;

    @Spy
    private AppointmentsService appointmentsServiceSpy;

    @InjectMocks
    private AppointmentService appointmentService;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        appointmentsServiceSpy = spy(appointmentsService);
        mockUriInfo();
        mockHttpResponse();
    }

    private void mockUriInfo() {
        mockUriInfo = mock(UriInfo.class);
        when(mockUriInfo.getRequestUri()).thenReturn(
            URI.create("http://localhost:8080"));
    }

    private void mockHttpResponse() {
        mockHttpResponse = mock(HttpResponse.class);
        when(mockHttpResponse.getInputStream()).thenReturn(
            IOUtils.toInputStream("{\"appointment\":{}}"));
    }

    @Test
    public void testGetAppointment() throws Exception {
        when(mockDataConnector.getAppointmentById(Mockito.anyString())).
            thenReturn(mockHttpResponse);
        doReturn(new Appointment()).when(appointmentsServiceSpy).validateResponse(Mockito.any(HttpResponse.class), Mockito.any(UriInfo.class));
        appointmentServiceSpy.getAppointment(mockUriInfo, "12345");
        verify(mockDataConnector).getAppointmentById(Mockito.anyString());
    }
}

I'm still getting an exception on the actual appointmentServiceSpy.getAppointment call, but I think that is a separate issue related to how I am using the spy. For the purposes of the original question, using a spy with a doReturn().when() statement resolves the issue of the method being erroneously called when using the when().thenReturn() statement.

Community
  • 1
  • 1
DanHam
  • 340
  • 2
  • 17