测试
Spring for GraphQL 为通过 HTTP、WebSocket 和 RSocket 测试 GraphQL 请求以及直接针对服务器进行测试提供专门的支持。
要使用此功能,请将 spring-graphql-test
添加到您的构建中
-
Gradle
-
Maven
dependencies {
// ...
testImplementation 'org.springframework.graphql:spring-graphql-test:1.3.5'
}
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<version>1.3.5</version>
<scope>test</scope>
</dependency>
</dependencies>
GraphQlTester
GraphQlTester
是一个契约,声明了一个独立于底层传输的用于测试 GraphQL 请求的通用工作流程。这意味着无论底层传输是什么,都使用相同的 API 测试请求,任何特定于传输的配置都在构建时进行。
要创建一个通过客户端执行请求的 GraphQlTester
,您需要以下扩展之一
要创建一个在服务器端(不使用客户端)执行测试的 GraphQlTester
每个都定义了一个与传输相关的 Builder
。所有构建器都继承自一个通用的基础 GraphQlTester Builder
,其中包含与所有扩展相关的选项。
HTTP
HttpGraphQlTester
使用 WebTestClient 通过 HTTP 执行 GraphQL 请求,无论是否使用实时服务器,具体取决于 WebTestClient
的配置方式。
要在 Spring WebFlux 中进行测试,无需实时服务器,请指向您声明 GraphQL HTTP 端点的 Spring 配置
AnnotationConfigWebApplicationContext context = ...
WebTestClient client =
WebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
要在 Spring MVC 中进行测试,无需实时服务器,请使用 MockMvcWebTestClient
执行相同的操作
AnnotationConfigWebApplicationContext context = ...
WebTestClient client =
MockMvcWebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
或者针对在特定端口上运行的实时服务器进行测试
WebTestClient client =
WebTestClient.bindToServer()
.baseUrl("http://localhost:8080/graphql")
.build();
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
创建 HttpGraphQlTester
后,您可以开始使用相同的 API 执行请求,而与底层传输无关。如果您需要更改任何特定于传输的详细信息,请在现有的 HttpSocketGraphQlTester
上使用 mutate()
来创建一个具有自定义设置的新实例
WebTestClient.Builder clientBuilder =
WebTestClient.bindToServer()
.baseUrl("http://localhost:8080/graphql");
HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
// Use tester...
HttpGraphQlTester anotherTester = tester.mutate()
.headers((headers) -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherTester...
WebSocket
WebSocketGraphQlTester
通过共享的 WebSocket 连接执行 GraphQL 请求。它使用 Spring WebFlux 中的 WebSocketClient 构建,您可以按如下方式创建它
String url = "http://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();
WebSocketGraphQlTester
是面向连接且多路复用的。每个实例都会为其所有请求建立自己的单一共享连接。通常,您会希望每个服务器只使用一个实例。
创建 WebSocketGraphQlTester
后,您可以开始使用相同的 API 执行请求,而与底层传输无关。如果您需要更改任何特定于传输的详细信息,请在现有的 WebSocketGraphQlTester
上使用 mutate()
来创建一个具有自定义设置的新实例
URI url = URI.create("ws://localhost:8080/graphql");
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
// Use tester...
WebSocketGraphQlTester anotherTester = tester.mutate()
.headers((headers) -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherTester...
WebSocketGraphQlTester
提供了一个 stop()
方法,您可以使用它来关闭 WebSocket 连接,例如在测试运行后。
RSocket
RSocketGraphQlTester
使用 spring-messaging 中的 RSocketRequester
通过 RSocket 执行 GraphQL 请求
URI url = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);
RSocketGraphQlTester client = RSocketGraphQlTester.builder()
.clientTransport(transport)
.build();
RSocketGraphQlTester
是面向连接且多路复用的。每个实例都会为其所有请求建立自己的单一共享会话。通常,您会希望每个服务器只使用一个实例。您可以使用 tester 上的 stop()
方法来显式关闭会话。
创建 RSocketGraphQlTester
后,您可以开始使用相同的 API 执行请求,而与底层传输无关。
ExecutionGraphQlService
很多时候,在服务器端测试 GraphQL 请求就足够了,无需使用客户端通过传输协议发送请求。要直接针对 ExecutionGraphQlService
进行测试,请使用 ExecutionGraphQlServiceTester
扩展
ExecutionGraphQlService service = ...
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service);
创建 ExecutionGraphQlServiceTester
后,您可以开始使用相同的 API 执行请求,而与底层传输无关。
ExecutionGraphQlServiceTester.Builder
提供了一个选项来自定义 ExecutionInput
详细信息
ExecutionGraphQlService service = ...
ExecutionId executionId = ExecutionId.generate();
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.builder(service)
.configureExecutionInput((executionInput, builder) -> builder.executionId(executionId).build())
.build();
WebGraphQlHandler
ExecutionGraphQlService
扩展允许您在服务器端进行测试,无需客户端。然而,在某些情况下,让服务器端传输处理与给定的模拟传输输入交互会很有用。
WebGraphQlTester
扩展允许您在将请求交给 ExecutionGraphQlService
执行之前,通过 WebGraphQlInterceptor
链处理请求
WebGraphQlHandler handler = ...
WebGraphQlTester tester = WebGraphQlTester.create(handler);
此扩展的构建器允许您定义 HTTP 请求详细信息
WebGraphQlHandler handler = ...
WebGraphQlTester tester = WebGraphQlTester.builder(handler)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
创建 WebGraphQlTester
后,您可以开始使用相同的 API 执行请求,而与底层传输无关。
请求
一旦您拥有 GraphQlTester
,就可以开始测试请求了。以下执行了一个针对项目的查询,并使用 JsonPath 从响应中提取项目发布版本
String document =
"""
{
project(slug:"spring-framework") {
releases {
version
}
}
}
""";
graphQlTester.document(document)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
JsonPath 是相对于响应的 "data" 部分的。
您还可以在类路径下的 "graphql-test/"
目录下创建扩展名为 .graphql
或 .gql
的文档文件,并通过文件名引用它们。
例如,假设在 src/main/resources/graphql-test
中有一个名为 projectReleases.graphql
的文件,其内容如下
query projectReleases($slug: ID!) {
project(slug: $slug) {
releases {
version
}
}
}
然后您可以使用
graphQlTester.documentName("projectReleases") (1)
.variable("slug", "spring-framework") (2)
.execute()
.path("projectReleases.project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
1 | 引用名为 "project" 的文件中的文档。 |
2 | 设置 slug 变量。 |
这种方法也适用于加载查询的片段(fragment)。片段是可重用的字段选择集,可以避免请求文档中的重复。例如,我们可以在多个查询中使用 …releases
片段
query frameworkReleases {
project(slug: "spring-framework") {
name
...releases
}
}
query graphqlReleases {
project(slug: "spring-graphql") {
name
...releases
}
}
此片段可以在单独的文件中定义以便重用
fragment releases on Project {
releases {
version
}
}
然后您可以将此片段与查询文档一起发送
graphQlTester.documentName("projectReleases") (1)
.fragmentName("releases") (2)
.execute()
.path("frameworkReleases.project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
1 | 从 "projectReleases.graphql" 加载文档 |
2 | 从 "releases.graphql" 加载片段并将其附加到文档 |
IntelliJ 的 "JS GraphQL" 插件支持带有代码补全的 GraphQL 查询文件。 |
如果请求没有任何响应数据(例如 mutation),请使用 executeAndVerify
代替 execute
来验证响应中没有错误
graphQlTester.query(query).executeAndVerify();
有关错误处理的更多详细信息,请参阅错误。
嵌套路径
默认情况下,路径是相对于 GraphQL 响应的 "data" 部分的。您还可以向下嵌套到某个路径,并检查相对于该路径的多个路径,如下所示
graphQlTester.document(document)
.execute()
.path("project", (project) -> project (1)
.path("name").entity(String.class).isEqualTo("spring-framework")
.path("releases[*].version").entityList(String.class).hasSizeGreaterThan(1));
1 | 使用回调来检查相对于 "project" 的路径。 |
订阅
要测试订阅,请调用 executeSubscription
而不是 execute
来获取响应流,然后使用 Project Reactor 中的 StepVerifier
来检查流
Flux<String> greetingFlux = tester.document("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class); // decode at JSONPath
StepVerifier.create(greetingFlux)
.expectNext("Hi")
.expectNext("Bonjour")
.expectNext("Hola")
.verifyComplete();
订阅仅支持 WebSocketGraphQlTester,或服务器端的 ExecutionGraphQlService
和 WebGraphQlHandler
扩展。
错误
当您使用 verify()
时,响应中 "errors" 键下的任何错误都会导致断言失败。要抑制特定错误,请在 verify()
之前使用错误过滤器
graphQlTester.document(query)
.execute()
.errors()
.filter((error) -> error.getMessage().equals("ignored error"))
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
您可以在构建器级别注册错误过滤器,以应用于所有测试
WebGraphQlTester graphQlTester = WebGraphQlTester.builder(handler)
.errorFilter((error) -> error.getMessage().equals("ignored error"))
.build();
如果您想验证某个错误确实存在,并且与 filter
相反,如果不存在则抛出断言错误,那么请改用 expect
graphQlTester.document(query)
.execute()
.errors()
.expect((error) -> error.getMessage().equals("expected error"))
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
您还可以通过 Consumer
检查所有错误,这样做也会将它们标记为已过滤,这样您就可以进一步检查响应中的数据
graphQlTester.document(document)
.execute()
.errors()
.satisfy((errors) ->
assertThat(errors)
.anyMatch((error) -> error.getMessage().contains("ignored error"))
);