简体   繁体   中英

Writing a unit test for a method with two HTTP calls with Mockito and jUnit

I have three methods,

  • void save(String a, String b)
  • String getId(String a, Pojo p)
  • void update(String a, String b, StringEntity c)

Here are the implementations(not the full implementation),

String getId(String accessToken, Pojo p) {

    //Note that the pojo is for formatting the payload for the request, nothing more

    HttpResponse response = httpRequestService.makeGetRequest(url, TOKEN_PREFIX, accessToken,
            CONTENT_TYPE);
    if (response.getStatusLine().getStatusCode() == 200) {
        log.debug("Success");
    }

    //Code for getting the id from the response

    return id;
}

void update(String accassToken, String id, StringEntity payload) {

    HttpResponse response = httpRequestService.makePutRequest(url + id,
            TOKEN_PREFIX, accessToken, CONTENT_TYPE, payload);

    if (response.getStatusLine().getStatusCode() == 200) {
        log.debug("Success");
    }
}

void save(String accessToken, String payload) {

    //The getId() is called here
    String id = getId(/*arguments*/);

    if(id == null) {
        log.error("Error");
    } else {
        //The update() is called here
        update(/*arguments*/);
    }
}

As mentioned, the getId and update methods are called inside save method and both getId and update methods have HTTP calls.

I have to write a unit test for the save() method. This is what I tried.

    //This is an interface with methods to call HTTP requests.
    HttpRequestService httpRequestService = Mockito.mock(HttpRequestService.class);

    //Constructor injection
    ClassWithMethods a = new ClasswithMethods(httpRequestService);

    HttpResponse responseGet = Mockito.mock(HttpResponse.class);
    StatusLine statusLineGet = Mockito.mock(StatusLine.class);
    HttpEntity httpEntity = Mockito.mock(HttpEntity.class);
    Mockito.when(responseGet.getStatusLine()).thenReturn(statusLineGet);
    Mockito.when(statusLineGet.getStatusCode()).thenReturn(200);
    Mockito.when(responseGet.getEntity()).thenReturn(httpEntity);
    Mockito.when(httpEntity.getContent()).thenReturn(IOUtils.toInputStream(stream));
    Mockito.when(httpRequestService.makeGetRequest(url, TOKEN_PREFIX, accessToken,
            CONTENT_TYPE).thenReturn(responseGet);

    HttpResponse responsePut = Mockito.mock(HttpResponse.class);
    StatusLine statusLinePut = Mockito.mock(StatusLine.class);
    Mockito.when(responsePut.getStatusLine()).thenReturn(statusLinePut);
    Mockito.when(statusLinePut.getStatusCode()).thenReturn(200);
    Mockito.when(httpRequestService.makePutRequest(url + id, TOKEN_PREFIX,
            accessToken, CONTENT_TYPE, payloadEntity).thenReturn(responsePut);

    a.save(accessToken, payload);

But when testing, the responsePut returns a null. The arguments are also matching.

Problem : How to test this save method which calls two HTTP methods?

This may not be the best way to do this. If there better ways to test a method like this, please suggest.

Thank you

Note that, httpRequestService is an interface with methods for HTTP calls and the constructor injection is used too.

You should extract the following into a separate method (same goes for PUT):

HttpResponse response = httpRequestService.makeGetRequest(url, TOKEN_PREFIX, accessToken,
            CONTENT_TYPE);

Then you can stub that method using mockito's:

when(obj.methodGET(...)).thenReturns(...)

Remember : your unit-tests should not make calls over-the-wire!

EDIT

private HttpResponse get(String url, ...) {
    return httpRequestService.makeGetRequest(url, TOKEN_PREFIX, accessToken,
            CONTENT_TYPE);
}


private HttpResponse put(String url, ...) {
    return httpRequestService.makePutRequest(url + id,
        TOKEN_PREFIX, accessToken, CONTENT_TYPE, payload);
}

And then:

...
when(mockedStatusLineObj.getStatusCode()).thenReturns(200);
HttpResponse mockedGet = mock(HttpResponse.class);
when(mockedGet.getStatusLine()).thenReturns(mockedStatusLineObj);
when(object.get(...)).thenReturns(mockedGet);
when(object.put(...)).thenReturns(...);

About your way of testing

Actually, your test setup is fine but complicated to read.
A test has to keep readable and simple.
Otherwise understanding and maintaining it becomes hard. You want to mock HttpRequestService .
It is a good way if you want to unit-test your class and don't want consider integration concerns.
But the way you mock dependencies of the mocked HttpRequestService makes the code not readable :

HttpResponse responsePut = Mockito.mock(HttpResponse.class);
StatusLine statusLinePut = Mockito.mock(StatusLine.class);
Mockito.when(responsePut.getStatusLine()).thenReturn(statusLinePut);
Mockito.when(statusLinePut.getStatusCode()).thenReturn(200);

And you repeat this at each invocation.
Extract them in a method :

public HttpResponse createMockedHttpResponse(String url){
    HttpResponse responsePut = Mockito.mock(HttpResponse.class);
    StatusLine statusLinePut = Mockito.mock(StatusLine.class);
    Mockito.when(responsePut.getStatusLine()).thenReturn(statusLinePut);
    Mockito.when(statusLinePut.getStatusCode()).thenReturn(200);
    return responsePut;
}

About the test failure

It just throws a NullPointerException at the line,

if (response.getStatusLine().getStatusCode() == 200) in the update() method.

When I debugged it, it shows the responsePut is null.

If I look your actual code, these invocations in the class under test :

getStatusLine().getStatusCode()

are correctly mocked here :

HttpResponse responsePut = Mockito.mock(HttpResponse.class);
StatusLine statusLinePut = Mockito.mock(StatusLine.class);
Mockito.when(responsePut.getStatusLine()).thenReturn(statusLinePut);
Mockito.when(statusLinePut.getStatusCode()).thenReturn(200);

By elimination, it means that the recorded behavior at the next statement was not encountered:

Mockito.when(httpRequestService.makePutRequest(url + id, TOKEN_PREFIX,
        accessToken, CONTENT_TYPE, payloadEntity).thenReturn(responsePut);

So ensure you that the record behavior for makePutRequest() and the actual invocation of makePutRequest() have exactly same / equals() passed parameters.
If some parameters are not really required for the test logic, don't hesitate to pass Mockito.any()

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