【问题标题】:Writing a unit test for a method with two HTTP calls with Mockito and jUnit使用 Mockito 和 jUnit 为具有两个 HTTP 调用的方法编写单元测试
【发布时间】:2017-11-08 11:03:54
【问题描述】:

我有三种方法,

  • 无效保存(字符串 a,字符串 b)
  • String getId(String a, Pojo p)
  • 无效更新(字符串 a,字符串 b,字符串实体 c)

这里是实现(不是完整的实现),

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*/);
    }
}

如前所述,getId 和 update 方法在 save 方法中调用,getId 和 update 方法都有 HTTP 调用。

我必须为 save() 方法编写一个单元测试。这是我尝试过的。

    //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);

但是在测试时,responsePut 返回一个空值。参数也匹配。

问题:如何测试这个调用两个HTTP方法的save方法?

这可能不是最好的方法。如果有更好的方法来测试这样的方法,请提出建议。

谢谢

请注意,httpRequestService 是一个带有 HTTP 调用方法的接口,也使用了构造函数注入。

【问题讨论】:

  • "但是在测试的时候,responsePut 返回了一个空值。" 一个变量没有返回任何东西。请在实际行为中更加明确,并显示失败测试消息。
  • @davidxxx,它只是在 update() 方法中的 if (response.getStatusLine().getStatusCode() == 200) 行抛出 NullPointerException。当我调试它时,它显示 responsePut 为空。
  • 识别错误行对于理解或猜测原因很重要。我做了一个回答。

标签: java unit-testing junit mocking mockito


【解决方案1】:

您应该将以下内容提取到一个单独的方法中(PUT 也是如此):

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

然后你可以使用 mockito 来存根该方法:

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

记住:您的单元测试应该通过网络进行调用!

编辑

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

然后:

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

【讨论】:

  • 那些已经在不同的方法中,PUT 在 'update()' 方法中,GET 在 'getId()' 方法中。你的意思是应该单独调用这些方法,而不是在同一个 'save()' 方法中调用它们吗?
  • @Agent47 不,不是:查看编辑以更好地理解我在说什么
  • @alfasin 将方法提取到与私有方法相同的类中意味着您必须使用 spy() 或任何“技巧”来模拟被测对象的方法。我觉得实际的OP设计还是不错的。
  • @davidxxx 当然,如果您可以使用间谍来模拟您无法访问的对象 - 那太好了。我不知道怎么做,但我很想学习!
  • @alfasin,但是按照我的做法,在将Mockito.anyString()Mockito.any(StringEntity.class) 作为适当的参数添加到以下内容之后,我可以通过测试。 Mockito.when(httpRequestService.makePutRequest(url + id, TOKEN_PREFIX, accessToken, CONTENT_TYPE, payloadEntity).thenReturn(responsePut); 我知道参数必须匹配才能正确返回模拟响应。但是我检查了这些论点是否有任何关系,我看不出有什么区别。添加真正的参数而不是 Mockito.any() 不会通过测试。为什么?
【解决方案2】:

关于您的测试方式

实际上,您的测试设置很好,但阅读起来很复杂。
测试必须保持可读性和简单性。
否则理解和维护它会变得很困难。 你想模拟HttpRequestService
如果您想 unit-test 您的班级并且不想考虑集成问题,这是一个好方法。
但是你模拟被模拟的HttpRequestService 的依赖项的方式使得代码不可读:

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

您在每次调用时重复此操作。
在方法中提取它们:

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

关于测试失败

它只是在线路上抛出一个NullPointerException

if (response.getStatusLine().getStatusCode() == 200)update() 方法。

当我调试它时,它显示 responsePut 为空。

如果我查看您的实际代码,这些在被测类中的调用:

getStatusLine().getStatusCode()

在这里被正确地模拟了:

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

因此请确保makePutRequest() 的记录行为和makePutRequest() 的实际调用具有完全相同/equals() 传递的参数。
如果某些参数对于测试逻辑来说并不是真正需要的,请不要犹豫,通过Mockito.any()

【讨论】:

  • 是的,添加 Mockito.any() 后,我可以通过测试。但有了真正的论据,它就失败了。但我也找不到论点的不同之处。 :-(
  • 您可以通过测试any() 的单个参数来找到不匹配的参数,并对下一个参数执行相同的操作,直到找到所有不匹配的参数。
  • @alfasin 从不。我的重构建议是提取单元测试类的方法,而不是被测类的方法。此外,正如我在您的回答中解释说应该避免这种做法的评论那样,提出它是没有意义的。
猜你喜欢
  • 2021-11-17
  • 2017-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-13
  • 1970-01-01
  • 2017-09-06
  • 1970-01-01
相关资源
最近更新 更多