測試
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 執行請求,而與底層傳輸無關。
請求
一旦您擁有 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
片段
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);
您可以在構建器級別註冊錯誤過濾器,以應用於所有測試
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"))
);