测试

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

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

  • Gradle

  • Maven

dependencies {
	// ...
	testImplementation 'org.springframework.graphql:spring-graphql-test:1.3.0'
}
<dependencies>
	<!-- ... -->
	<dependency>
		<groupId>org.springframework.graphql</groupId>
		<artifactId>spring-graphql-test</artifactId>
		<version>1.3.0</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 配置

ApplicationContext context = ... ;

WebTestClient client =
		WebTestClient.bindToApplicationContext(context)
				.configureClient()
				.baseUrl("/graphql")
				.build();

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

要在 Spring MVC 中进行测试,无需实时服务器,请使用 MockMvcWebTestClient 执行相同的操作

ApplicationContext 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() 创建一个具有自定义设置的新实例

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 = ... ;
WebSocketClient client = ... ;

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 uri = 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 = ... ;
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.builder(service)
		.configureExecutionInput((executionInput, builder) -> builder.executionId(id).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 - 请求执行完成前等待的时间,超时之前。

Requests

拥有 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.gql 的文档文件,放在类路径上的 "graphql-test/" 下,并通过文件名引用它们。

例如,假设在 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("project.releases[*].version")
		.entityList(String.class)
		.hasSizeGreaterThan(1);
1 引用名为“project”的文件中的文档。
2 设置 slug 变量。

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));
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.query(query)
		.execute()
		.errors()
		.filter(error -> ...)
		.verify()
		.path("project.releases[*].version")
		.entityList(String.class)
		.hasSizeGreaterThan(1);

您可以在构建器级别注册错误过滤器,以将其应用于所有测试。

WebGraphQlTester graphQlTester = WebGraphQlTester.builder(client)
		.errorFilter(error -> ...)
		.build();

如果您想验证错误确实存在,并且与filter相反,如果错误不存在,则抛出断言错误,那么请使用expect代替。

graphQlTester.query(query)
		.execute()
		.errors()
		.expect(error -> ...)
		.verify()
		.path("project.releases[*].version")
		.entityList(String.class)
		.hasSizeGreaterThan(1);

您也可以通过Consumer检查所有错误,这样做也会将它们标记为已过滤,因此您也可以检查响应中的数据。

graphQlTester.query(query)
		.execute()
		.errors()
		.satisfy(errors -> {
			// ...
		});