GraphQL

GraphQL (英語) は本質的に HTTP であるため、キー verifier とマッピング tool=graphql を持つ追加の metadata エントリを含む標準 HTTP 契約を作成することで、その契約を作成できます。

Groovy
import org.springframework.cloud.contract.spec.Contract

Contract.make {

	request {
		method(POST())
		url("/graphql")
		headers {
			contentType("application/json")
		}
		body('''
{
	"query":"query queryName($personName: String!) {\\n  personToCheck(name: $personName) {\\n    name\\n    age\\n  }\\n}\\n\\n\\n\\n",
	"variables":{"personName":"Old Enough"},
	"operationName":"queryName"
}
''')
	}

	response {
		status(200)
		headers {
			contentType("application/json")
		}
		body('''\
{
  "data": {
    "personToCheck": {
      "name": "Old Enough",
      "age": "40"
    }
  }
}
''')
	}
	metadata(verifier: [
	        tool: "graphql"
	])
}
YAML
---
request:
  method: "POST"
  url: "/graphql"
  headers:
    Content-Type: "application/json"
  body:
    query: "query queryName($personName: String!) { personToCheck(name: $personName)
      {         name    age  } }"
    variables:
      personName: "Old Enough"
    operationName: "queryName"
  matchers:
    headers:
      - key: "Content-Type"
        regex: "application/json.*"
        regexType: "as_string"
response:
  status: 200
  headers:
    Content-Type: "application/json"
  body:
    data:
      personToCheck:
        name: "Old Enough"
        age: "40"
  matchers:
    headers:
      - key: "Content-Type"
        regex: "application/json.*"
        regexType: "as_string"
name: "shouldRetrieveOldEnoughPerson"
metadata:
  verifier:
    tool: "graphql"

メタデータセクションを追加すると、デフォルトの WireMock スタブの構築方法が変更されます。Spring Cloud Contract リクエストマッチャーを使用するようになりました。GraphQL リクエストの query 部分は、空白を無視して実際のリクエストと比較されます。

プロデューサー側のセットアップ

プロデューサー側の構成は次のようになります。

Maven
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <testMode>EXPLICIT</testMode>
        <baseClassForTests>com.example.BaseClass</baseClassForTests>
    </configuration>
</plugin>
Gradle
contracts {
	testMode = "EXPLICIT"
	baseClassForTests = "com.example.BaseClass"
}

基本クラスは、ランダムなポートで実行されるアプリケーションをセットアップします。

基本クラス
@SpringBootTest(classes = ProducerApplication.class,
		properties = "graphql.servlet.websocket.enabled=false",
		webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class BaseClass {

	@LocalServerPort int port;

	@BeforeEach
	public void setup() {
		RestAssured.baseURI = "http://localhost:" + port;
	}
}

コンシューマー側のセットアップ

GraphQL API のコンシューマー側テストの例。

コンシューマー側テスト
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class BeerControllerGraphQLTest {

	@RegisterExtension
	static StubRunnerExtension rule = new StubRunnerExtension()
			.downloadStub("com.example","beer-api-producer-graphql")
			.stubsMode(StubRunnerProperties.StubsMode.LOCAL);

	private static final String REQUEST_BODY = "{\n"
			+ "\"query\":\"query queryName($personName: String!) {\\n  personToCheck(name: $personName) {\\n    name\\n    age\\n  }\\n}\","
			+ "\"variables\":{\"personName\":\"Old Enough\"},\n"
			+ "\"operationName\":\"queryName\"\n"
			+ "}";

	@Test
	public void should_send_a_graphql_request() {
		ResponseEntity<String> responseEntity = new RestTemplate()
				.exchange(RequestEntity
						.post(URI.create("http://localhost:" + rule.findStubUrl("beer-api-producer-graphql").getPort() + "/graphql"))
						.contentType(MediaType.APPLICATION_JSON)
						.body(REQUEST_BODY), String.class);

		BDDAssertions.then(responseEntity.getStatusCodeValue()).isEqualTo(200);

	}
}