測試
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
,其中包含與傳輸協定相關的選項。所有 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);
此擴充功能的 builder 可讓您定義 HTTP 請求詳細資訊
WebGraphQlHandler handler = ...
WebGraphQlTester tester = WebGraphQlTester.builder(handler)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
建立 WebGraphQlTester
後,您可以使用相同的 API 開始 執行請求,而無需考慮底層傳輸協定。
請求
一旦您有了 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
片段
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);
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 或伺服器端 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);
您可以在 builder 層級註冊錯誤篩選器,以套用至所有測試
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"))
);