Test a REST API with Java – 用Java测试一个REST API

最后修改: 2011年 10月 13日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll focus on the basic principles and mechanics of testing a REST API with live Integration Tests (with a JSON payload).

在本教程中,我们将重点介绍用实时集成测试测试REST API的基本原则和机制(带JSON有效载荷)。

Our main goal is to provide an introduction to testing the basic correctness of the API, and we’ll use the latest version of the GitHub REST API for the examples.

更多我们的主要目标是提供有关测试 API 基本正确性的介绍,我们将使用最新版本的 GitHub REST API 进行示例。

For an internal application, this kind of testing will usually run as a late step in a Continuous Integration process, consuming the REST API after it’s already been deployed.

对于一个内部应用,这种测试通常作为持续集成过程的后期步骤运行,在它已经被部署后消耗REST API。

When testing a REST resource, there are usually a few orthogonal responsibilities the tests should focus on:

当测试一个REST资源时,通常有几个正交的责任,测试应该关注。

  • the HTTP response code
  • other HTTP headers in the response
  • the payload (JSON, XML)

Each test should only focus on a single responsibility and include a single assertion. Focusing on a clear separation always has benefits, but when doing this kind of black box testing, it’s even more important because the general tendency is to write complex test scenarios in the very beginning.

每个测试应该只关注一个责任,并包括一个断言。关注明确的分离总是有好处的,但在做这种黑盒测试时,它甚至更重要,因为一般的趋势是在一开始就写复杂的测试方案。

Another important aspect of integration tests is adherence to the Single Level of Abstraction Principle; we should write the logic within a test at a high level. Details such as creating the request, sending the HTTP request to the server, dealing with IO, etc., shouldn’t be done inline, but via utility methods.

集成测试的另一个重要方面是遵守单层抽象原则;我们应该在高水平上编写测试的逻辑。诸如创建请求、向服务器发送HTTP请求、处理IO等细节,不应该内联完成,而应该通过实用方法完成。

2. Testing the Status Code

2.测试状态代码

@Test
public void givenUserDoesNotExists_whenUserInfoIsRetrieved_then404IsReceived()
  throws ClientProtocolException, IOException {
 
    // Given
    String name = RandomStringUtils.randomAlphabetic( 8 );
    HttpUriRequest request = new HttpGet( "https://api.github.com/users/" + name );

    // When
    HttpResponse httpResponse = HttpClientBuilder.create().build().execute( request );

    // Then
    assertThat(
      httpResponse.getStatusLine().getStatusCode(),
      equalTo(HttpStatus.SC_NOT_FOUND));
}

This is a rather simple test. It verifies that a basic happy path is working, without adding too much complexity to the test suite.

这是一个相当简单的测试。它验证了一个基本的快乐路径是否工作,没有给测试套件增加太多复杂性。

If, for whatever reason, it fails, then we don’t need to look at any other test for this URL until we fix it.

如果,不管什么原因,它失败了,那么我们就不需要看这个URL的任何其他测试,直到我们修复它。

3. Testing the Media Type

3.测试媒体类型

@Test
public void 
givenRequestWithNoAcceptHeader_whenRequestIsExecuted_thenDefaultResponseContentTypeIsJson()
  throws ClientProtocolException, IOException {
 
   // Given
   String jsonMimeType = "application/json";
   HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );

   // When
   HttpResponse response = HttpClientBuilder.create().build().execute( request );

   // Then
   String mimeType = ContentType.getOrDefault(response.getEntity()).getMimeType();
   assertEquals( jsonMimeType, mimeType );
}

This ensures that the Response actually contains JSON data.

这确保了Response实际上包含JSON数据。

As we can see, we’re following a logical progression of tests. First is the Response Status Code (to ensure the request was OK), and then the Media Type of the Response. Only in the next test will we look at the actual JSON payload.

正如我们所看到的,我们正在遵循测试的逻辑进展。首先是响应状态代码(以确保请求是OK的),然后是响应的媒体类型。只有在下一个测试中,我们才会看一下实际的JSON有效载荷。

4. Testing the JSON Payload

4.测试JSON有效载荷

@Test
public void 
  givenUserExists_whenUserInformationIsRetrieved_thenRetrievedResourceIsCorrect()
  throws ClientProtocolException, IOException {
 
    // Given
    HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );

    // When
    HttpResponse response = HttpClientBuilder.create().build().execute( request );

    // Then
    GitHubUser resource = RetrieveUtil.retrieveResourceFromResponse(
      response, GitHubUser.class);
    assertThat( "eugenp", Matchers.is( resource.getLogin() ) );
}

In this case, the default representation of the GitHub resources is JSON, but usually, the Content-Type header of the response should be tested alongside the Accept header of the request. The client asks for a particular type of representation via Accept, which the server should honor.

在这种情况下,GitHub资源的默认表示法是JSON,但通常,响应的Content-Type头应与请求的Accept头一起测试。客户端通过Accept要求特定类型的表示,服务器应予以尊重。

5. Utilities for Testing

5.测试的实用程序

We’ll use Jackson 2 to unmarshall the raw JSON String into a type-safe Java Entity:

我们将使用Jackson 2将原始JSON字符串解构为一个类型安全的Java实体。

public class GitHubUser {

    private String login;

    // standard getters and setters
}

We’re only using a simple utility to keep the tests clean, readable, and at a high level of abstraction:

我们只用一个简单的工具来保持测试的简洁、可读性和高层次的抽象性。

public static <T> T retrieveResourceFromResponse(HttpResponse response, Class<T> clazz) 
  throws IOException {
 
    String jsonFromResponse = EntityUtils.toString(response.getEntity());
    ObjectMapper mapper = new ObjectMapper()
      .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    return mapper.readValue(jsonFromResponse, clazz);
}

Notice that Jackson is ignoring unknown properties that the GitHub API is sending our way. This is simply because the Representation of a User Resource on GitHub gets pretty complex, and we don’t need any of that information here.

请注意,Jackson正在忽略GitHub API发送给我们的未知属性。这只是因为GitHub上的用户资源表示法变得相当复杂,而我们在这里不需要任何这些信息。

6. Dependencies

6.依赖性

The utilities and tests make use of the following libraries, all of which are available in Maven central:

实用程序和测试使用了以下库,所有这些库都可以在Maven中心找到。

7. Conclusion

7.结论

This is only one part of what the complete integration testing suite should be. The tests focus on ensuring basic correctness for the REST API, without going into more complex scenarios.

这只是完整的集成测试套件应该有的一部分。测试的重点是确保REST API的基本正确性,而不是进入更复杂的场景。

For example, we didn’t cover the following: discoverability of the API, consumption of different representations for the same Resource, etc.

例如,我们没有涵盖以下内容:API的可发现性、同一资源的不同表现形式的消费,等等。

The implementation of all of these examples and code snippets can be found over on Github. This is a Maven-based project, so it should be easy to import and run as it is.

所有这些例子和代码片断的实现都可以在Github上找到over。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。