[[features-graphql]] = GraphQL include::partial$_attributes.adoc[] Since https://graphql.org/[GraphQL] is essentially HTTP you can write a contract for it by creating a standard HTTP contract with an additional `metadata` entry with key `verifier` and a mapping `tool=graphql`. ==== [source,groovy,indent=0,subs="verbatim",role="primary"] .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" ]) } ---- [source,yml,indent=0,subs="verbatim",role="secondary"] .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" ---- ==== Adding the metadata section will change the way the default, WireMock stub is built. It will now use the Spring Cloud Contract request matcher, so that e.g. the `query` part of the GraphQL request gets compared against the real request by ignoring whitespaces. [[features-graphql-producer]] == Producer Side Setup On the producer side your configuration can look as follows. ==== [source,xml,indent=0,subs="verbatim",role="primary"] .Maven ---- org.springframework.cloud spring-cloud-contract-maven-plugin ${spring-cloud-contract.version} true EXPLICIT com.example.BaseClass ---- [source,groovy,indent=0,subs="verbatim",role="secondary"] .Gradle ---- contracts { testMode = "EXPLICIT" baseClassForTests = "com.example.BaseClass" } ---- ==== The base class would set up the application running on a random port. ==== [source,java,indent=0,subs="verbatim"] .Base Class ---- @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; } } ---- ==== [[features-graphql-consumer]] == Consumer Side Setup Example of a consumer side test of the GraphQL API. ==== [source,java,indent=0,subs="verbatim"] .Consumer Side Test ---- @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 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); } } ---- ====