1. 概述

在本篇文章中,我们将深入了解 Jersey Test Framework,并探讨如何利用它快速编写集成测试。

正如我们之前所了解的,**Jersey 是一个用于开发 RESTful Web 服务的开源框架**。如果你对 Jersey 还不熟悉,可以先阅读我们的另一篇文章 使用 Jersey 和 Spring 构建 REST API 来入门。

2. 应用配置

Jersey Test Framework 是一个用于验证服务端组件实现是否正确的工具。正如你即将看到的,它提供了一种快速且无痛的方式来编写集成测试,并且能够很好地处理与 HTTP API 的通信。

✅ 同时,它几乎开箱即用,非常容易集成到基于 Maven 的项目中。虽然框架主要基于 JUnit,但也支持 TestNG,因此在大多数环境中都能使用。

接下来,我们将介绍如何在项目中添加必要的依赖。

2.1. Maven 依赖

首先,我们需要在 pom.xml 中添加 Jersey Test Framework 的核心依赖:

<dependency>
    <groupId>org.glassfish.jersey.test-framework</groupId>
    <artifactId>jersey-test-framework-core</artifactId>
    <version>3.1.1</version>
    <scope>test</scope>
</dependency>

最新版本可以从 Maven Central 获取。

几乎所有的 Jersey 测试都会使用默认的 Grizzly 容器工厂,因此我们还需要添加以下依赖:

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>3.1.1</version>
    <scope>test</scope>
</dependency>

同样,最新版本可以从 Maven Central 获取。

3. 快速上手

在这一节中,我们将介绍编写一个简单测试所需的基本步骤。

我们从测试一个简单的 Greetings 接口开始:

@Path("/greetings")
public class Greetings {

    @GET
    @Path("/hi")
    public String getHiGreeting() {
        return "hi";
    }
}

3.1. 配置测试类

现在我们来定义测试类:

public class GreetingsResourceIntegrationTest extends JerseyTest {

    @Override
    protected Application configure() {
        return new ResourceConfig(Greetings.class);
    }
    //...
}

✅ 注意:要使用 Jersey Test Framework 编写测试,测试类必须继承 JerseyTest 类。

接着我们重写 configure 方法,返回一个自定义的资源配置对象,其中只包含我们要测试的 Greetings 接口。

3.2. 编写第一个测试

我们来测试一下这个接口的 GET 请求:

@Test
public void givenGetHiGreeting_whenCorrectRequest_thenResponseIsOkAndContainsHi() {
    Response response = target("/greetings/hi").request()
        .get();

    assertEquals("Http Response should be 200: ", Status.OK.getStatusCode(), response.getStatus());
    assertEquals("Http Content-Type should be: ", MediaType.TEXT_HTML, response.getHeaderString(HttpHeaders.CONTENT_TYPE));

    String content = response.readEntity(String.class);
    assertEquals("Content of ressponse is: ", "hi", content);
}

✅ 我们可以完全访问 HTTP 响应对象 —— 所以可以检查状态码是否成功,也可以读取响应体内容。

具体来说,上面的测试做了以下几件事:

  1. /greetings/hi 发送 GET 请求
  2. 验证响应的状态码和 Content-Type
  3. 验证响应内容是否为 "hi"

4. 测试 GET 接口获取资源

现在我们已经了解了测试的基本流程,接下来测试一个简单的 Fruit API,这个 API 在之前的 Jersey MVC 支持 文章中已经介绍过。

4.1. 获取原始 JSON 响应

下面的例子中,我们将响应体当作普通的 JSON 字符串处理:

@Test
public void givenFruitExists_whenSearching_thenResponseContainsFruit() {
    final String json = target("fruit/search/strawberry").request()
        .get(String.class);
    assertThat(json, containsString("{\"name\":\"strawberry\",\"weight\":20}"));
}

4.2. 获取映射后的实体对象

我们也可以将响应直接映射为实体类对象,例如:

@Test
public void givenFruitExists_whenSearching_thenResponseContainsFruitEntity() {
    final Fruit entity = target("fruit/search/strawberry").request()
        .get(Fruit.class);

    assertEquals("Fruit name: ", "strawberry", entity.getName());
    assertEquals("Fruit weight: ", Integer.valueOf(20), entity.getWeight());
}

这一次,我们在 get 方法中指定了目标类型,框架会自动将响应体转换为 Fruit 对象。

5. 测试 POST 接口创建资源

在 API 中创建新资源时,通常会使用 POST 请求。接下来我们将展示如何测试这部分接口。

5.1. 提交原始 JSON

我们先测试通过提交 JSON 字符串来创建一个新的 Fruit 资源:

@Test
public void givenCreateFruit_whenJsonIsCorrect_thenResponseCodeIsCreated() {
    Response response = target("fruit/created").request()
        .post(Entity.json("{\"name\":\"strawberry\",\"weight\":20}"));

    assertEquals("Http Response should be 201 ", Status.CREATED.getStatusCode(), response.getStatus());
    assertThat(response.readEntity(String.class), containsString("Fruit saved : Fruit [name: strawberry colour: null]"));
}

✅ 这里我们使用了 Entity.json() 方法,它能方便地将 JSON 字符串封装为 Entity 对象。

5.2. 提交实体对象

正如 GET 请求一样,我们也可以直接提交实体对象:

@Test
public void givenCreateFruit_whenFruitIsInvalid_thenResponseCodeIsBadRequest() {
    Fruit fruit = new Fruit("Blueberry", "purple");
    fruit.setWeight(1);

    Response response = target("fruit/create").request(MediaType.APPLICATION_JSON_TYPE)
        .post(Entity.entity(fruit, MediaType.APPLICATION_JSON_TYPE));

    assertEquals("Http Response should be 400 ", 400, response.getStatus());
    assertThat(response.readEntity(String.class), containsString("Fruit weight must be 10 or greater"));
}

这次我们使用 Entity.entity() 方法提交 Fruit 实体对象,并指定了媒体类型为 JSON。

5.3. 使用 POST 提交表单

最后,我们来看一个使用 POST 提交表单的例子:

@Test
public void givenCreateFruit_whenFormContainsNullParam_thenResponseCodeIsBadRequest() {
    Form form = new Form();
    form.param("name", "apple");
    form.param("colour", null);
    
    Response response = target("fruit/create").request(MediaType.APPLICATION_FORM_URLENCODED)
        .post(Entity.form(form));

    assertEquals("Http Response should be 400 ", 400, response.getStatus());
    assertThat(response.readEntity(String.class), containsString("Fruit colour must not be null"));
}

这里我们使用 Entity.form() 方法提交了一个包含多个参数的表单。

6. 测试其他 HTTP 方法

有时候我们还需要测试 PUT 或 DELETE 等其他 HTTP 方法。✅ Jersey Test Framework 当然也支持这些操作。

下面是一个简单的 PUT 示例:

@Test
public void givenUpdateFruit_whenFormContainsBadSerialParam_thenResponseCodeIsBadRequest() {
    Form form = new Form();
    form.param("serial", "2345-2345");

    Response response = target("fruit/update").request(MediaType.APPLICATION_FORM_URLENCODED)
        .put(Entity.form(form));

    assertEquals("Http Response should be 400 ", 400, response.getStatus());
    assertThat(response.readEntity(String.class), containsString("Fruit serial number is not valid"));
}

调用 request() 方法后,我们就可以调用任意 HTTP 方法了。

7. 附加功能

Jersey Test Framework 提供了一些额外的配置项,可以帮助调试和测试。

下面的例子展示了如何启用一些调试功能:

public class FruitResourceIntegrationTest extends JerseyTest {

    @Override
    protected Application configure() {
        enable(TestProperties.LOG_TRAFFIC);
        enable(TestProperties.DUMP_ENTITY);
        //...

✅ 我们可以在配置应用时启用这些功能,例如 LOG_TRAFFICDUMP_ENTITY,它们会在测试运行时输出详细的请求/响应日志,非常实用。

8. 支持的容器

前面我们提到,Jersey Test Framework 默认使用 Grizzly 作为测试容器。✅ 但它也支持其他几种容器:

  • In-Memory 容器
  • Oracle JDK 自带的 HttpServer
  • Simple 容器(org.simpleframework.http)
  • Jetty 容器(org.eclipse.jetty)

更多关于如何配置这些容器的信息,请参考官方文档 这里

9. 总结

本文中,我们探索了 Jersey Test Framework 的基本使用方式。首先介绍了如何配置框架,然后展示了如何为一个简单 API 编写测试。

接着我们测试了 GET 和 POST 接口,最后还介绍了框架的一些附加功能以及支持的容器选项。

✅ 完整源码可以在 GitHub 上获取:https://github.com/eugenp/tutorials/tree/master/web-modules/jersey


原始标题:Exploring the Jersey Test Framework