简体   繁体   中英

Test class with nested dependencies

I'm testing a class with nested (Autowired) dependencies. The class implements businesslogic for making alterations in the backend. Specifically the test should assert that when a certain backend call returns an error:

  • No more backend calls are made
  • The response object returns an error

I dont know how the do the latter. My class looks something like this:

public class Handler {

    @Autowired
    private DaoImpl dao;

    @Autowired
    private SpecificUtil util1;

    @Autowired
    private GeneralUtil util2;

    @Autowired
    private Helper helper;

    public Response doSomethingClever(Request request) {
    // calls to dao
    // logic with help of util and helper classes
    }
}

The testclass:

public class HandlerTest {

@Spy
private DaoImpl dao;

@Mock
private SpecificUtil util1;

@Mock
private GeneralUtil util2;

@Mock
private Helper helper;

@InjectMocks
Handler handler;

@Test
public void testDoSomethingClever() {
    // set up dao to fail
    QueryResult error = getErrorResult();
    org.mockito.Mockito.when(dao.queryBackEnd(any(SpecificQuery.class))).thenReturn(error);

    // perform query
    Request request = getTestRequest();
    Response errorResponse = handler.doSomethingClever(request);

    // verify that either:
    // Response has errors - fails 
    // because helper classes are mocks, have not set the error 
    assertNotNull(response.getErrorMessage());

    // the method setErrors of Response was called once - fails 
    //because the setError was called earlier!
    Response spyResponse = Mockito.spy(errorResponse);
    verify(spyResponse, times(1)).setError(anyString);

    //verify no other calls are made except the queryBackEnd call - this part  works
    org.mockito.Mockito.verify(dao).queryBackEnd(any(SpecificQuery.class));
    org.mockito.Mockito.verifyNoMoreInteractions(dao);
}

}

The Response object is created in the Handler class. If i check the returned response, no interactions will be recorded by Mockito, because the interactions have taken place prior to calling Mockito.spy.

I have tried making it an integration test, by using @Spy instead of @Mock. The idea is to instantiate all the nested dependencies except for the dao and get a proper Response to test for errors. But this does not work because some of the @Autowired helper and utility classes also have @Autowired dependencies and these nested dependencies are not instantiated during testing.

Is there any way to inject @Spy objects into other @Spy objects with Mockito? Or is there some other solution in this case? Sould i write my own mockobjects?

A unit test should test the code of a specific unit only (here the Handler class). This includes interacting with the dependencies.

From what you wrote in your question and comments your handle method looks something along these lines:

public class Handler {

    @Autowired
    private DaoImpl dao;

    @Autowired
    private Util util;

    public Response doSomethingClever(Request request) {
        SpecificQuery specificQuery = new SpecificQuery();
        specificQuery.setSomeData(request.getSomeData());

        IntermidiateResponse intermidiateResponse = dao.queryBackEnd(specificQuery);
        Response response = util.processIntermidiateResult(intermidiateResult);

        return response;
    }
}

There are a few interactions to unit test here:

  • Given a Request instance assert that DaoImpl::queryBackEnd method is called with a SpecificQuery instance that has someData property set to someData property from the Request object
  • Given a mocked IntermidiateResponse being returned from the DaoImpl::queryBackEnd method assert that this result is passed on to the Util::processIntermidiateResult method
  • Given a mocked Response being returned from the Util::processIntermidiateResult method assert that this is exactly what gets returned from the handle method

This way you have 100% coverage on the Handler::handle method. If you have some more calls in the response processing pipeline you test them all accordingly.

Hope this answers your question. Good luck

There are two options, one is to test the Handler in isolation, mocking everything else. See the answer by jannis . The other option is to control/mock the in- and output of the Handler, and treat the Handler and all its utility classes as a black box.

I have opted for this last option because it means that i can refactor the utility-classes, and the Handler class itself, and that tests will succeed as long as i don't change what the Handler does. It does mean that i have departed somewhat from doing unit testing and that i'm really doing more of an integration test.

For this i mock the Dao class, so that i can have it return an error at the desired point and so that i can assert that no further calls are made after the error. It is the only class i mock, so i need to inject it into the Handler. This is possible with Springs ReflectionTestUtils (I did'nt know about this yesterday)

The testcode then becomes shorter:

public class HandlerTest {

  @Autowired
  private Handler handler;

  @Test
  public void testDoSomethingClever() {

  // set up dao to fail
  Dao  mockDao = org.mockito.Mockito.mock(DaoImpl.class);
  QueryResult error = getErrorResult();
  org.mockito.Mockito.when(dao.queryBackEnd(any  (SpecificQuery.class))).thenReturn(error);

  // inject the dao
  ReflectionTestUtils.setField(handler, "dao", mockDao);

  // perform query
  Request request = getTestRequest();
  Response errorResponse = handler.doSomethingClever(request);

  // verify that Response has errors 
  assertNotNull(response.getErrorMessage());

  //verify no other calls are made except the queryBackEnd call
  org.mockito.Mockito.verify(dao).queryBackEnd(any(SpecificQuery.class));
  org.mockito.Mockito.verifyNoMoreInteractions(dao);
 }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM