测试
Spring for GraphQL 提供了专门的支持,用于测试通过 HTTP、WebSocket 和 RSocket 进行的 GraphQL 请求,以及直接针对服务器进行测试。
要使用此功能,请将 spring-graphql-test 添加到您的构建中:
- Gradle
- Maven
dependencies {
// ...
testImplementation 'org.springframework.graphql:spring-graphql-test:2.0.2'
}
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<version>2.0.2</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 是面向连接且支持多路复用的。每个实例会为所有请求建立自己独立的、共享的会话。通常,每个服务器只需使用一个实例。你可以使用测试器上的 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 执行请求,而无需关心底层的传输方式。
Builder
GraphQlTester 定义了一个父级 Builder,其中包含了所有受支持传输协议的构建器共有的配置选项。它允许您配置以下内容:
-
errorFilter- 用于抑制预期错误的断言函数,以便您能检查响应数据。 -
documentSource- 一种从类路径文件或其他任意位置加载请求文档的策略。 -
responseTimeout- 在超时前等待请求执行完成的时间。
GraphQlTransport transport = ...
Predicate<ResponseError> errorFilter = ...
ClassPathResource resource = new ClassPathResource("custom-folder/");
DocumentSource documentSource = new ResourceDocumentSource(resource);
GraphQlTester tester = GraphQlTester.builder(transport)
.documentSource(documentSource)
.errorFilter(errorFilter)
.responseTimeout(Duration.ofSeconds(5))
.build();
请求
一旦你有了 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);
请参考名为 "project" 的文件中的文档。
设置
slug变量。
这种方法同样适用于为查询加载片段。片段是可复用的字段选择集,能够避免请求文档中的重复。例如,我们可以在多个查询中使用 …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);
从 "projectReleases.graphql" 加载文档
从 "releases.graphql" 加载片段并将其附加到文档
IntelliJ 的 "JS GraphQL" 插件支持 GraphQL 查询文件并提供代码补全功能。
如果请求没有任何响应数据,例如变更操作,请使用 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));
使用回调函数来检查相对于"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"))
);