テスト

Spring for GraphQL は、HTTP、WebSocket、RSocket を介した GraphQL リクエストのテスト、およびサーバーに対する直接のテストの専用サポートを提供します。

これを利用するには、ビルドに spring-graphql-test を追加します。

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

GraphQlTester

GraphQlTester は、基礎となるトランスポートから独立した GraphQL リクエストをテストするための共通のワークフローを宣言する契約です。つまり、基礎となるトランスポートに関係なく、リクエストは同じ API でテストされ、トランスポート固有のものはビルド時に構成されます。

クライアント経由でリクエストを実行する GraphQlTester を作成するには、次の拡張のいずれかが必要です。

クライアントなしでサーバー側でテストを実行する GraphQlTester を作成するには:

それぞれが、トランスポートに関連するオプションを持つ Builder を定義します。すべてのビルダーは、共通のベース GraphQlTester Builder から拡張され、すべての拡張に関連するオプションがあります。

HTTP

HttpGraphQlTester は WebTestClient を使用して、WebTestClient の構成方法に応じて、ライブサーバーの有無にかかわらず、HTTP 経由で GraphQL リクエストを実行します。

ライブサーバーなしで 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("http://localhost: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 = "http://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();

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

WebSocketGraphQlTester は接続指向で多重化されています。各インスタンスは、すべてのリクエストに対して独自の単一の共有接続を確立します。通常、サーバーごとに 1 つのインスタンスのみを使用します。

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 は、WebSocket 接続を閉じるために使用できる stop() メソッドを提供します。テストの実行後。

RSocket

RSocketGraphQlTester は spring-messaging の RSocketRequester を使用して、RSocket 経由で GraphQL リクエストを実行します。

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

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

RSocketGraphQlTester は接続指向で多重化されています。各インスタンスは、すべてのリクエストに対して独自の単一の共有セッションを確立します。通常、サーバーごとに 1 つのインスタンスのみを使用します。テスターで 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 を使用してリクエストの実行を開始できます。

ビルダー

GraphQlTester は、すべての拡張機能のビルダーに共通の構成オプションを持つ親 Builder を定義します。以下を設定できます。

  • errorFilter - 予期されるエラーを抑制するための述語。これにより、レスポンスのデータをインスペクションできます。

  • documentSource - クラスパス上のファイルまたは他の場所からのリクエストのドキュメントをロードするための戦略。

  • responseTimeout - リクエストの実行がタイムアウトするまでの待機時間。

要求

GraphQlTester を取得したら、リクエストのテストを開始できます。以下は、プロジェクトのクエリを実行し、JsonPath [GitHub] (英語) を使用して、レスポンスからプロジェクトリリースバージョンを抽出します。

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

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

JsonPath は、レスポンスの「データ」セクションに関連しています。

クラスパス上の "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("project.releases[*].version")
		.entityList(String.class)
		.hasSizeGreaterThan(1);
1 "project" という名前のファイル内のドキュメントを参照してください。
2slug 変数を設定します。

IntelliJ の "JS GraphQL" プラグインは、コード補完 で GraphQL クエリファイルをサポートします。

リクエストにレスポンスデータがない場合。レスポンスにエラーがないことを確認するには、execute の代わりに executeAndVerify を使用します。

graphQlTester.query(query).executeAndVerify();

エラー処理の詳細については、エラーを参照してください。

入れ子になったパス

デフォルトでは、パスは GraphQL レスポンスの「データ」セクションに対する相対パスです。次のように、パスまでネストして、そのパスに関連する複数のパスをインスペクションすることもできます。

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 コールバックを使用して、「プロジェクト」に関連するパスをインスペクションします。

サブスクリプション

サブスクリプションをテストするには、execute の代わりに executeSubscription を呼び出してレスポンスのストリームを取得し、プロジェクト 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.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 -> {
			// ...
		});