【问题标题】:How to unit test HttpClient retry logic using junit如何使用junit对HttpClient重试逻辑进行单元测试
【发布时间】:2019-08-02 21:18:51
【问题描述】:

我正在使用 apache http 客户端来使用服务,并且我需要根据超时和响应代码重试请求。 为此,我实现了如下代码。如何为超时和响应代码场景编写重试逻辑的 junit 测试。我想以这样的方式编写单元测试,当我发送任何 post/get 请求时,如果它返回 429 错误代码响应或任何 TimeOutException,我应该确保重试逻辑正确执行。我不知道如何为重试逻辑编写单元测试。 通过谷歌搜索,我找到了以下链接,但它对我没有帮助。

Unit testing DefaultHttpRequestRetryHandler

我正在使用 junit、Mockito 编写单元测试和 PowerMock 来模拟静态方法。

public class GetClient {

private static CloseableHttpClient httpclient;

public static CloseableHttpClient getInstance() {

        try {
            HttpClientBuilder builder = HttpClients.custom().setMaxConnTotal(3)
                    .setMaxConnPerRoute(3);
            builder.setRetryHandler(retryHandler());
            builder.setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy() {
            int waitPeriod = 200;

            @Override
            public boolean retryRequest(final HttpResponse response, final int executionCount,
                final HttpContext context) {

                int statusCode = response.getStatusLine().getStatusCode();
                return (((statusCode == 429) || (statusCode >= 300 && statusCode <= 399))
                            && (executionCount < 3));
            }

            @Override
            public long getRetryInterval() {
                return waitPeriod;
            }
            });            

            httpclient = builder.build();

        } catch (Exception e) {
            //handle exception
        }
        return httpclient;
    }

     private static HttpRequestRetryHandler retryHandler() {
        return (exception, executionCount, context) -> {
            if (executionCount > maxRetries) {
                // Do not retry if over max retry count
                return false;
            }
            if (exception instanceof InterruptedIOException) {                
                // Timeout
                return true;
            }
            if (exception instanceof UnknownHostException) {
                // Unknown host
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {
                // Connection refused
                return false;
            }
            if (exception instanceof SSLException) {
                // SSL handshake exception
                return false;
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                // Retry if the request is considered idempotent
                return true;
            }
            return false;
        };
    }
}

public CloseableHttpResponse uploadFile(){    
        CloseableHttpClient httpClient = GetClient.getInstance();
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(post);
        } catch (Exception ex) {
            //handle exception
        }
        return response;    
   }

谁能帮帮我。

【问题讨论】:

  • 你是如何为这个类编写单元测试的?你有没有成功调用uploadFile 的测试?
  • 我也有兴趣看到 uploadFile() 的简单运行测试。首先这里的结构,大量调用静态项,不利于编写单元测试,而且PowerMock不是我的朋友(WhiteBox除外)。我的总体策略是将 httpclient 构建为模拟,为正确的方法抛出异常(例如,使用 Mockito.when(...).thenThrow(...).thenReturn(...) )并让模拟使用 Mockito.doCallRealMethod() 作为正确的方法。但总而言之,没有看到 uploadFile 方法的工作版本,这只是一个猜测。

标签: java junit mockito apache-httpclient-4.x powermock


【解决方案1】:

您的 httpClient 有一个“目标”网址,比如说 localhost:1234。你要测试的是你的重试代码,所以你不应该接触 httpClient 本身(因为它不是你的组件,你也不应该测试它。)

因此,手头的问题是当您的 localhost:1234 响应有问题时,您希望看到将运行的重试逻辑(不是您的实现..如果它没有以正确的 conf 运行,则其问题)有效..您唯一需要做的就是模拟“localhost:1234”!

此工具http://wiremock.org/ 是执行此操作的完美选择。您可以为您的目标网址创建存根,并根据您喜欢的几乎任何内容给出一系列响应。

您的代码应如下所示 在打电话之前uploadFile

    stubFor(post(urlEqualTo("/hash"))
        .willReturn(aResponse()
            .withStatus(200)
            .withBody(externalResponse)));

和 打电话后uploadFile

并验证步骤以验证到达模拟端点的模拟请求

    Assert.assert* //... whatever you want to assert in your handlers / code / resposnes
    verify(postRequestedFor(urlEqualTo("/hash")));

【讨论】:

  • 本指南是我在wiremock baeldung.com/introduction-to-wiremock 上的最爱之一。看看,让我知道你是否同意。对于多个响应,只需在存根上重复 willReturn 方法。它将以与 junit 相同的方式工作。每次调用存根时,它将提供下一行 willReturn
  • 你的wiremock服务器将运行在类似localhost:9999的地方。这在 urlEqualsTo() 中被省略,需要通过一些配置提供给您的代码(因为不应硬编码 url,它应该已经实现以注入配置)尾随 url /fetch/token 可能是硬编码的您的项目的核心路径可以按原样使用。所以 urlEqualsTo("/fetch/token").您应该在测试中使用绝对路径并定义所有内容以减少无意义/错误的测试。所以开始wiremock。带有 urlEqualsTo("/fetch/token") add 2-3 willReturn 的存根运行你的测试然后验证!
  • JaCoCo 不应该关心您的测试框架(wiremock 和其他东西),因为它会增强您的实际代码来创建覆盖范围。我已经有一段时间没有使用配置的 JaCoCo,所以我无法真正回答你的问题。盲目猜测问题可能是您为重试逻辑实现的类。这将由 httpClient 而不是您执行。因此 JaCoCo 可能无法对其进行增强以进行监控。这又是一个疯狂的猜测!
  • 似乎问题是在外部类运行时没有选择内部类测试。
  • 知道有用。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-27
相关资源
最近更新 更多