12.3.1 测试 GET 请求

对于 recentTacos() 方法,我们想声明的一件事是,如果为 /api/tacos?recent 路径发出了 HTTP GET 请求,那么响应将包含一个不超过 12 个 tacos 的 JSON 数据。下面清单中的测试类是一个很好的开始。

程序清单 12.1 使用 WebTestClient 测试 DesignTacoController

package tacos.web.api;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;
import tacos.data.TacoRepository;

public class TacoControllerTest {

  @Test
  public void shouldReturnRecentTacos() {
    Taco[] tacos = {
      testTaco(1L), testTaco(2L),
      testTaco(3L), testTaco(4L),
      testTaco(5L), testTaco(6L),
      testTaco(7L), testTaco(8L),
      testTaco(9L), testTaco(10L),
      testTaco(11L), testTaco(12L),
      testTaco(13L), testTaco(14L),
      testTaco(15L), testTaco(16L)};
  Flux<Taco> tacoFlux = Flux.just(tacos);

  TacoRepository tacoRepo = Mockito.mock(TacoRepository.class);
  when(tacoRepo.findAll()).thenReturn(tacoFlux);

  WebTestClient testClient = WebTestClient.bindToController(
    new TacoController(tacoRepo))
    .build();

  testClient.get().uri("/api/tacos?recent")
    .exchange()
    .expectStatus().isOk()
    .expectBody()
      .jsonPath("$").isArray()
      .jsonPath("$").isNotEmpty()
      .jsonPath("$[0].id").isEqualTo(tacos[0].getId().toString())
      .jsonPath("$[0].name").isEqualTo("Taco 1")
      .jsonPath("$[1].id").isEqualTo(tacos[1].getId().toString())
      .jsonPath("$[1].name").isEqualTo("Taco 2")
      .jsonPath("$[11].id").isEqualTo(tacos[11].getId().toString())
      .jsonPath("$[11].name").isEqualTo("Taco 12")
      .jsonPath("$[12]").doesNotExist();
  }

  ...
}

shouldReturnRecentTacos() 方法做的第一件事是以 Flux<Taco> 的形式设置测试数据。然后,这个 Flux 作为模拟 TacoRepository 的 findAll() 方法的返回值。

对于将由 Flux 发布的 Taco 对象,它们是用一个名为 testTaco() 的方法创建的,当给定一个数字时,该方法将生成一个 Taco 对象,其 ID 和名称基于该数字。testTaco() 方法实现如下:

private Taco testTaco(Long number) {
  Taco taco = new Taco();
  taco.setId(number != null ? number.toString(): "TESTID");
  taco.setName("Taco " + number);
  List<Ingredient> ingredients = new ArrayList<>();
  ingredients.add(
    new Ingredient("INGA", "Ingredient A", Type.WRAP));
  ingredients.add(
    new Ingredient("INGB", "Ingredient B", Type.PROTEIN));
  taco.setIngredients(ingredients);
  return taco;
}

为了简单起见,所有的测试 tacos 都有相同的两种成分。但它们的 ID 和名字将由给定的号码决定。

同时,在 shouldReturnRecentTacos() 方法中,实例化了一个 TacoController,将模拟的 TacoRepository 注入构造函数。Controller 被赋予 WebTestClient.bindToController() 以创建 WebTestClient 的实例。

完成所有设置后,现在可以使用 WebTestClient 向 /api/tacos?recent 提交 GET 请求,并验证响应是否满足预期。调用 get().uri(“/api/tacos?recent”) 描述要发出的请求。然后调用 exchange() 提交请求,该请求将由绑定到 TacoController 的 Controller 进行处理。

最后,可以确认响应与预期一致。通过调用 expectStatus(),可以断言响应具有 HTTP 200(OK) 状态代码。之后,将看到对 jsonPath() 的几个调用,这些调用断言响应体中的 JSON 具有它应该具有的值。最后的断言检查第 12 个元素(在基于零的数组中)是否不存在,因为结果不应超过 12 个元素。

如果 JSON 返回很复杂,包含大量数据或高度嵌套的数据,那么使用 jsonPath() 可能会很无聊。实际上,为了节省空间,清单 12.1 中已经省略了对 jsonPath() 的许多调用。对于那些使用 jsonPath() 可能很笨拙的情况,WebTestClient 提供了 json(),它接受包含 json 的 String 参数来对响应进行响应。

例如,假设在一个名为 recent-tacos.json 的文件中创建了完整的响应 JSON,并将其放在路径 /tacos 下的类路径中。然后重写 WebTestClient 断言,如下所示:

ClassPathResource recentsResource =
  new ClassPathResource("/tacos/recent-tacos.json");
String recentsJson = StreamUtils.copyToString(
  recentsResource.getInputStream(), Charset.defaultCharset());

testClient.get().uri("/api/tacos?recent")
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .expectStatus().isOk()
  .expectBody()
    .json(recentsJson);

因为 json() 接受 String,所以必须首先将类路径资源加载到 String 对象中。谢天谢地,Spring 中的 StreamUtils 使 copyToString() 的使用变得简单。copyToString() 返回的 String 将包含响应请求时预期的整个 JSON。将它赋给 json() 方法可以确保 Controller 产生正确的输出。

WebTestClient 提供的另一个选项,允许将响应体与值列表进行比较。expectBodyList() 方法接受指示列表中元素类型的 ClassParameterizedTypeReference,并返回要针对其进行断言的 istBodySpec 对象。使用 expectBodyList(),可以重写测试以使用用于创建模拟 TacoRepository 的相同测试数据的子集:

testClient.get().uri("/api/tacos?recent")
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .expectStatus().isOk()
  .expectBodyList(Taco.class)
    .contains(Arrays.copyOf(tacos, 12));

在这里,断言响应体包含的列表,与在测试方法开始时创建的原始 Taco 数组的前 12 个元素,具有相同的元素。

results matching ""

    No results matching ""