测试

Spring for GraphQL 提供了专门的支持,用于测试通过 HTTP、WebSocket 和 RSocket 的 GraphQL 请求,以及直接针对服务器进行测试。

要使用此功能,请将 spring-graphql-test 添加到您的构建中

  • Gradle

  • Maven

dependencies {
	// ...
	testImplementation 'org.springframework.graphql:spring-graphql-test:1.3.3'
}
<dependencies>
	<!-- ... -->
	<dependency>
		<groupId>org.springframework.graphql</groupId>
		<artifactId>spring-graphql-test</artifactId>
		<version>1.3.3</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("https://127.0.0.1:8080/graphql")
				.build();

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

创建 HttpGraphQlTester 后,您可以开始使用相同的 API 执行请求,而与底层传输无关。如果您需要更改任何传输特定的细节,请在现有的 HttpSocketGraphQlTester 上使用 mutate() 创建一个具有自定义设置的新实例。

WebTestClient.Builder clientBuilder =
		WebTestClient.bindToServer()
				.baseUrl("https://127.0.0.1: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 = "https://127.0.0.1:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();

WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();

WebSocketGraphQlTester 是面向连接且复用的。每个实例都为所有请求建立自己的单个共享连接。通常,您只希望每个服务器使用一个实例。

创建 WebSocketGraphQlTester 后,您可以开始使用相同的 API 执行请求,而与底层传输无关。如果您需要更改任何传输特定的细节,请在现有的 WebSocketGraphQlTester 上使用 mutate() 创建一个具有自定义设置的新实例。

URI url = URI.create("ws://127.0.0.1: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://127.0.0.1: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 - 请求执行完成前等待的时间,超过此时间将超时。

请求

拥有 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变量。

这种方法也适用于为您的查询加载片段。片段是可重用的字段选择集,可以避免在请求文档中重复。例如,我们可以在多个查询中使用…​releases片段

src/main/resources/graphql-documents/projectReleases.graphql
query frameworkReleases {
	project(slug: "spring-framework") {
		name
		...releases
	}
}
query graphqlReleases {
       project(slug: "spring-graphql") {
           name
           ...releases
       }
   }

此片段可以在单独的文件中定义以供重用

src/main/resources/graphql-documents/releases.graphql
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,或使用服务器端ExecutionGraphQlServiceWebGraphQlHandler扩展。

错误

当您使用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"))
		);