Mock request/post with mockito

2019-06-05 04:01发布

问题:

I am having trouble covering the following function 'postJson' with tests (JUnit/Mockito), and can't find a way to mock the line response = getTarget(path).request().post(entity, Map.class);

//Constructor
public HttpService() {
    this.client = ClientBuilder.newClient();
}

Client client;

public Map<String, ?> postJson(String path, Map<String, ?> data){
    Map<String, ?> response = null;

    try {
        Entity<Map<String, ?>> entity = Entity.entity(data, MediaType.APPLICATION_JSON);
        response = getTarget(path).request().post(entity, Map.class);   
    } catch (Exception e) {
        LOG.error(e.toString());
    }

    return response;
}

public WebTarget getTarget(String path){
    return client.target(BASE_URI + path);
}

My test currently

@Test
public void postJsonTest(){
    assertEquals(null,new HttpService().postJson("", new HashMap<String,Integer>()));

    //Verifica se a função de comunicação com servidor é chamda
    Map<String,String> resposta = new HashMap<String,String>();
    HttpService mock = spy(HttpService.class);  

    assertEquals(mock.postJson("",resposta),null);

    Mockito.verify(mock,Mockito.atLeast(1)).postJson("", resposta);
    Mockito.verify(mock,Mockito.atLeast(1)).getTarget(Mockito.anyString());
}

I can't find a way to make the test code after the 'request()' onwards. Anyone can give me an example/explain how I can go about covering this function with mockito ?

回答1:

Given this additional constructor on HttpService:

public HttpService(Client client) {
    this.client = client;
}

The following test will pass:

@RunWith(MockitoJUnitRunner.class)
public class HttpServiceTest {

    @Mock
    private Client client;
    @Mock
    private WebTarget webTarget;
    @Mock
    private RequestEntity requestEntity;

    private HttpService httpService;

    @Before
    public void setUp() {
        this.httpService = new HttpService(client);
    }

    @Test
    public void postJsonTest() {
        String path = "/a/path";
        Map<String, ?> data = new HashMap<>();

        // the postJson method features this chained call: getTarget(path).request().post(entity, Map.class)
        // so we have to mock each object created in that chain

        // covers getTarget(path)
        Mockito.when(client.target(Mockito.anyString())).thenReturn(webTarget);

        // covers request()
        Mockito.when(webTarget.request()).thenReturn(requestEntity);

        // covers post()
        Map<String, Object> expected = new HashMap<>();

        Mockito.when(requestEntity.post(Mockito.any(Entity.class), Mockito.eq(Map.class))).thenReturn(expected);
        Map<String, ?> actual = httpService.postJson(path, data);
        Assert.assertSame(expected, actual);
    }
}

Notes:

  • This relies on providing a new HttpService constructor which accepts a Client instance.
  • The existing approach of instancing Client via a static method call inside the HttpService's no-arg constructor requires use of PowerMockito. A more test friendly approach is to provide a constructor which accepts a Client or perhaps a ClientFactory with the default implementation of the ClientFactory being ClientBuilder.newClient()
  • The postJson() method features a chained call (getTarget(path).request().post(entity, Map.class)) which requires us to mock each object returned in that chain i.e. Client, WebTarget and RequestEntity
  • My example skates over specifics such as the generic type of RequestEntity and the choice of exact argument matchers vs. general argument matchers. You'll be able to make the right choice there since you know most about (a) the specifics of your implementation and (b) the intention of your test but the above test at least shows you that mocking a chain of calls requires each object in that chain to be mocked.