測試

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("https://:8080/graphql")
				.build();

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

建立 HttpGraphQlTester 後,您可以開始使用相同的 API 執行請求,而與底層傳輸無關。如果您需要更改任何特定於傳輸的詳細資訊,請在現有的 HttpSocketGraphQlTester 上使用 mutate() 來建立一個具有自定義設定的新例項

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

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

WebSocketGraphQlTester 是面向連線且多路複用的。每個例項都會為其所有請求建立自己的單一共享連線。通常,您會希望每個伺服器只使用一個例項。

建立 WebSocketGraphQlTester 後,您可以開始使用相同的 API 執行請求,而與底層傳輸無關。如果您需要更改任何特定於傳輸的詳細資訊,請在現有的 WebSocketGraphQlTester 上使用 mutate() 來建立一個具有自定義設定的新例項

URI url = URI.create("ws://: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://: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 執行請求,而與底層傳輸無關。

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 變數。

這種方法也適用於載入查詢的片段(fragment)。片段是可重用的欄位選擇集,可以避免請求文件中的重複。例如,我們可以在多個查詢中使用 …​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"))
		);