【问题标题】:How to Test code based on HTTP Connections如何基于 HTTP 连接测试代码
【发布时间】:2017-06-23 03:58:01
【问题描述】:

我正在开发一个解析从 HTTP 端点检索到的 XML 的系统。当我考虑如何测试我的代码时,我不希望我的单元测试实际上向实时站点发出 HTTP 请求。这似乎是一种很好的做法。

所以我把读取端点内容的代码封装在这个类中,这样我就可以用 Mockito 模拟它了。但是现在,我该如何为这个类编写单元测试呢?刚刚把问题往下推了,现在还得和它抗衡。

我可以再次包装 URL 对象,但我只是在推卸责任。

我正在尝试遵循“干净代码”中的 TDD 的 3 条法则

  • 第一定律:在编写失败的单元测试之前,您不得编写生产代码。

  • 第二定律:你写的单元测试不能超过足以失败的程度。

  • 第三定律:您编写的生产代码不得超过足以通过当前失败的测试的数量。

完成这门课我已经违反了第一定律,但我不明白如何通过单元测试来解决这个问题。有什么建议吗?

/**
 * Fetches the content from an HTTP Resource
 */
public class HttpFetcher {

    /**
     * Gets the contents of an HTTP Endpoint using Basic Auth, similar to how Postman (chrome extenstion) does.
     *
     * @param username Username to authenticate with
     * @param password Password to authenticate with
     * @param url URL of the endpoint to read.
     * @return Contents read from the endpoint as a String.
     * @throws HttpException if any errors are encountered.
     */
    public String get(String username, String password, String url) {
        URLConnection connection;

        // Establish Connection
        try {
            connection = new URL(url).openConnection();
            String credentials = encodeCredentials(username, password);
            connection.setRequestProperty("Authorization", "Basic " + credentials);
        } catch (MalformedURLException e) {
            throw new HttpException(String.format("'%s' is not a valid URL.", e));
        } catch (IOException e) {
            throw new HttpException(String.format("Failed to connect to url: '%s'", url), e);
        }

        // Read the response
        try {
            String contents = readInputStream(connection.getInputStream());
            return contents;
        } catch (IOException e) {
            throw new HttpException(String.format("Failed to read from the url: '%s' ", url), e);
        }
    }

    private String encodeCredentials(String username, String password) {
        String credentials = String.format("%s:%s", username, password);
        String encodedCredentials = new String(Base64.encodeBase64(credentials.getBytes()));
        return encodedCredentials;
    }

    private String readInputStream(InputStream is) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
            return reader.lines().collect(Collectors.joining("\n"));
        }
    }
}

【问题讨论】:

    标签: java unit-testing


    【解决方案1】:

    您可以将 Connection 的创建移动到某个外部类,然后模拟该类:

    class HttpFetcher {
      private final ConnectionCreator connectionCreator;
    
      ...
    
      public HttpFetcher(ConnectionCreator connectionCreator) { this.connectionCreator = connectionCreator; }
      public String get(...) {
        ...
        try {
          connection = connectionCreator.createConnectionForUrl(url);
          ...
    

    这也是对单一职责原则的一个小改进:一个类用于从连接中获取数据,一个类用于实际创建连接。

    【讨论】:

    • 这个我喜欢。它给了我一个注入更多模拟的钩子,并且不需要我包含另一个模拟框架。作为奖励,我可以在不实际建立连接的情况下测试这个新类。谢谢!
    【解决方案2】:

    我强烈建议为此使用WireMock。这个工具可以用来为这些类型的测试精确地启动本地服务器。

    下面复制了一个这样的测试示例:

    @Test
    public void exampleTest() {
        stubFor(get(urlEqualTo("/my/resource"))
                .withHeader("Accept", equalTo("text/xml"))
                .willReturn(aResponse()
                    .withStatus(200)
                    .withHeader("Content-Type", "text/xml")
                    .withBody("<response>Some content</response>")));
    
        Result result = myHttpServiceCallingObject.doSomething();
    
        assertTrue(result.wasSuccessful());
    
        verify(postRequestedFor(urlMatching("/my/resource/[a-z0-9]+"))
                .withRequestBody(matching(".*<message>1234</message>.*"))
                .withHeader("Content-Type", notMatching("application/json")));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-08
      • 1970-01-01
      • 1970-01-01
      • 2019-03-11
      • 1970-01-01
      • 2010-09-22
      • 1970-01-01
      • 2018-03-29
      相关资源
      最近更新 更多