Testing

面向 GraphQL 的 Spring 为针对 HTTP、WebSocket 和 RSocket 测试 GraphQL 请求以及针对服务器直接测试提供了专门的支持。

Spring for GraphQL provides dedicated support for testing GraphQL requests over HTTP, WebSocket, and RSocket, as well as for testing directly against a server.

要使用此功能,请将 spring-graphql-test 添加到你的生成中:

To make use of this, add spring-graphql-test to your build:

  • Gradle

  • Maven

dependencies {
	// ...
	testImplementation 'org.springframework.graphql:spring-graphql-test:{spring-graphql-version}'
}
<dependencies>
	<!-- ... -->
	<dependency>
		<groupId>org.springframework.graphql</groupId>
		<artifactId>spring-graphql-test</artifactId>
		<version>{spring-graphql-version}</version>
		<scope>test</scope>
	</dependency>
</dependencies>

GraphQlTester

GraphQlTester 是一个契约,它声明了一个独立于底层传输的测试 GraphQL 请求的通用工作流。这意味着无论底层传输是什么,都会使用相同的 API 测试请求,并且在生成时配置任何传输特定信息。

GraphQlTester is a contract that declares a common workflow for testing GraphQL requests that is independent of the underlying transport. That means requests are tested with the same API no matter what the underlying transport, and anything transport specific is configured at build time.

要创建通过客户端执行请求的 GraphQlTester,你需要以下扩展之一:

To create a GraphQlTester that performs requests through a client, you need one of the following extensions:

要创建在服务器端执行测试的 GraphQlTester,而不使用客户端:

To create a GraphQlTester that performs tests on the server side, without a client:

每个都使用与传输相关的选项定义了 Builder。所有的构建程序都扩展自具有与所有扩展相关的选项的公共的基本的 GraphQlTester Builder

Each defines a Builder with options relevant to the transport. All builders extend from a common, base GraphQlTester Builder with options relevant to all extensions.

HTTP

HttpGraphQlTester 使用 WebTestClient 在没有或有实时服务器的情况下通过 HTTP 执行 GraphQL 请求,具体取决于 WebTestClient 的配置方式。

HttpGraphQlTester uses WebTestClient to execute GraphQL requests over HTTP, with or without a live server, depending on how WebTestClient is configured.

若要在 Spring WebFlux 中进行测试,没有实时服务器,请指向声明 GraphQL HTTP 端点的 Spring 配置:

To test in Spring WebFlux, without a live server, point to your Spring configuration that declares the GraphQL HTTP endpoint:

ApplicationContext context = ... ;

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

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

若要在 Spring MVC 中进行测试,没有实时服务器,使用`MockMvcWebTestClient`执行相同操作:

To test in Spring MVC, without a live server, do the same using MockMvcWebTestClient:

ApplicationContext context = ... ;

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

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

或针对在端口上运行的实时服务器进行测试:

Or to test against a live server running on a port:

WebTestClient client =
		WebTestClient.bindToServer()
				.baseUrl("http://localhost:8080/graphql")
				.build();

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

一旦创建了 HttpGraphQlTester,就可以使用相同的 API 开始 execute requests,而不需要底层的传输。如果您需要更改任何传输特定详细信息,请对现有的 HttpSocketGraphQlTester 使用 mutate() 以创建具有自定义设置的新实例:

Once HttpGraphQlTester is created, you can begin to execute requests using the same API, independent of the underlying transport. If you need to change any transport specific details, use mutate() on an existing HttpSocketGraphQlTester to create a new instance with customized settings:

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 构建的,可以按以下方式创建:

WebSocketGraphQlTester executes GraphQL requests over a shared WebSocket connection. It is built using the WebSocketClient from Spring WebFlux and you can create it as follows:

String url = "http://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();

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

`WebSocketGraphQlTester`面向连接,并且是多路复用的。每个实例为所有请求建立自己的单个共享连接。通常,你只想针对每个服务器使用单个实例。

WebSocketGraphQlTester is connection oriented and multiplexed. Each instance establishes its own single, shared connection for all requests. Typically, you’ll want to use a single instance only per server.

一旦创建了 WebSocketGraphQlTester,就可以使用相同的 API 开始 execute requests,而不需要底层的传输。如果您需要更改任何传输特定详细信息,请对现有的 WebSocketGraphQlTester 使用 mutate() 以创建具有自定义设置的新实例:

Once WebSocketGraphQlTester is created, you can begin to execute requests using the same API, independent of the underlying transport. If you need to change any transport specific details, use mutate() on an existing WebSocketGraphQlTester to create a new instance with customized settings:

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 连接,例如,在测试运行之后。

WebSocketGraphQlTester provides a stop() method that you can use to have the WebSocket connection closed, e.g. after a test runs.

RSocket

RSocketGraphQlTester`使用 spring-messaging 中的 `RSocketRequester 通过 RSocket 执行 GraphQL 请求:

RSocketGraphQlTester uses RSocketRequester from spring-messaging to execute GraphQL requests over RSocket:

URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);

RSocketGraphQlTester client = RSocketGraphQlTester.builder()
		.clientTransport(transport)
		.build();

`RSocketGraphQlTester`面向连接,并且是多路复用的。每个实例为所有请求建立自己的单个共享会话。通常,你只想针对每个服务器使用单个实例。你可以使用测试器上的`stop()`方法显式关闭会话。

RSocketGraphQlTester is connection oriented and multiplexed. Each instance establishes its own single, shared session for all requests. Typically, you’ll want to use a single instance only per server. You can use the stop() method on the tester to close the session explicitly.

一旦创建了 RSocketGraphQlTester,就可以使用相同的 API 开始 execute requests,而不需要底层的传输。

Once RSocketGraphQlTester is created, you can begin to execute requests using the same API, independent of the underlying transport.

ExecutionGraphQlService

许多时候直接在服务器端测试 GraphQL 请求就已足够,而无需使用客户端通过传输协议发送请求。若要针对`ExecutionGraphQlService`直接进行测试,请使用`ExecutionGraphQlServiceTester`扩展:

Many times it’s enough to test GraphQL requests on the server side, without the use of a client to send requests over a transport protocol. To test directly against a ExecutionGraphQlService, use the ExecutionGraphQlServiceTester extension:

ExecutionGraphQlService service = ... ;
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service);

一旦创建了 ExecutionGraphQlServiceTester,就可以使用相同的 API 开始 execute requests,而不需要底层的传输。

Once ExecutionGraphQlServiceTester is created, you can begin to execute requests using the same API, independent of the underlying transport.

`ExecutionGraphQlServiceTester.Builder`提供了一个选项来自定义`ExecutionInput`详细信息:

ExecutionGraphQlServiceTester.Builder provides an option to customize ExecutionInput details:

ExecutionGraphQlService service = ... ;
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.builder(service)
		.configureExecutionInput((executionInput, builder) -> builder.executionId(id).build())
		.build();

WebGraphQlHandler

ExecutionGraphQlService 扩展允许您在没有客户端的情况下在服务器端进行测试。但是,在某些情况下,使用给定的模拟传输输入涉及服务器端传输处理会很有用。

The ExecutionGraphQlService extension lets you test on the server side, without a client. However, in some cases it’s useful to involve server side transport handling with given mock transport input.

`WebGraphQlTester`扩展使你能够在传递给`ExecutionGraphQlService`执行请求之前,通过`WebGraphQlInterceptor`链处理请求:

The WebGraphQlTester extension lets you processes request through the WebGraphQlInterceptor chain before handing off to ExecutionGraphQlService for request execution:

WebGraphQlHandler handler = ... ;
WebGraphQlTester tester = WebGraphQlTester.create(handler);

此扩展的构建器允许你定义 HTTP 请求详细信息:

The builder for this extension allows you to define HTTP request details:

WebGraphQlHandler handler = ... ;

WebGraphQlTester tester = WebGraphQlTester.builder(handler)
		.headers(headers -> headers.setBasicAuth("joe", "..."))
		.build();

一旦创建了 WebGraphQlTester,就可以使用相同的 API 开始 execute requests,而不需要底层的传输。

Once WebGraphQlTester is created, you can begin to execute requests using the same API, independent of the underlying transport.

Builder

GraphQlTester`定义了一个父`Builder,其中包含所有扩展构建器的通用配置选项。它允许你配置以下内容:

GraphQlTester defines a parent Builder with common configuration options for the builders of all extensions. It lets you configure the following:

  • errorFilter - a predicate to suppress expected errors, so you can inspect the data of the response.

  • documentSource - a strategy for loading the document for a request from a file on the classpath or from anywhere else.

  • responseTimeout - how long to wait for request execution to complete before timing out.

Requests

一旦有了 GraphQlTester,你就可以开始对请求进行测试。下面是为项目执行查询并使用 JsonPath 从响应中提取项目发布版本的示例:

Once you have a GraphQlTester, you can begin to test requests. The below executes a query for a project and uses JsonPath to extract project release versions from the response:

String document = "{" +
		"  project(slug:\"spring-framework\") {" +
		"	releases {" +
		"	  version" +
		"	}"+
		"  }" +
		"}";

graphQlTester.document(document)
		.execute()
		.path("project.releases[*].version")
		.entityList(String.class)
		.hasSizeGreaterThan(1);

JsonPath 相对于响应的 “data” 部分。

The JsonPath is relative to the "data" section of the response.

您也可以在类路径的 “graphql-test/” 下创建扩展名为 .graphql.gql 的文档文件,并通过文件名引用它们。

You can also create document files with extensions .graphql or .gql under "graphql-test/" on the classpath and refer to them by file name.

例如,给定在 src/main/resources/graphql-test 中名为 projectReleases.graphql 的文件,其内容为:

For example, given a file called projectReleases.graphql in src/main/resources/graphql-test, with content:

query projectReleases($slug: ID!) {
	project(slug: $slug) {
		releases {
			version
		}
	}
}

然后,您可以使用:

You can then use:

graphQlTester.documentName("projectReleases") 1
		.variable("slug", "spring-framework") 2
		.execute()
		.path("project.releases[*].version")
		.entityList(String.class)
		.hasSizeGreaterThan(1);
1 Refer to the document in the file named "project".
2 Set the slug variable.

IntelliJ 的 “JS GraphQL” 插件支持带有代码补全功能的 GraphQL 查询文件。

The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion.

如果请求没有任何响应数据,例如变异,那么使用 executeAndVerify 代替 execute 来验证响应中没有错误:

If a request does not have any response data, e.g. mutation, use executeAndVerify instead of execute to verify there are no errors in the response:

graphQlTester.query(query).executeAndVerify();

有关错误处理的更多详细信息,请参见 Errors

See Errors for more details on error handling.

Nested Paths

默认情况下,路径相对于 GraphQL 响应的 “data” 部分。您还可以缩小到某个路径,并检查相对于它的多个路径,如下所示:

By default, paths are relative to the "data" section of the GraphQL response. You can also nest down to a path, and inspect multiple paths relative to it as follows:

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 Use a callback to inspect paths relative to "project".

Subscriptions

要测试订阅,请调用 executeSubscription 代替 execute 来获取响应流,然后使用 Project Reactor 中的 StepVerifier 来检查流:

To test subscriptions, call executeSubscription instead of execute to obtain a stream of responses and then use StepVerifier from Project Reactor to inspect the stream:

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 扩展一起受支持。

Subscriptions are supported only with WebSocketGraphQlTester , or with the server side ExecutionGraphQlService and WebGraphQlHandler extensions.

Errors

当您使用 verify() 时,响应中的 “errors” 键下的任何错误都将导致断言失败。要抑制特定错误,请在 verify() 之前使用错误筛选器:

When you use verify(), any errors under the "errors" key in the response will cause an assertion failure. To suppress a specific error, use the error filter before verify():

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

您可以在构建器级别注册错误筛选器,以便应用于所有测试:

You can register an error filter at the builder level, to apply to all tests:

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

如果您想验证是否存在某个错误,并且与 filter 相反,如果它不存在就抛出断言错误,那么使用 expect 代替:

If you want to verify that an error does exist, and in contrast to filter, throw an assertion error if it doesn’t, then use expect instead:

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

您还可通过 Consumer 检查所有错误,此操作也会将其标记为已筛选,因此您还可以检查响应中的数据:

You can also inspect all errors through a Consumer, and doing so also marks them as filtered, so you can then also inspect the data in the response:

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