Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added GraphQL support #1506

Merged
merged 2 commits into from Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 30 additions & 3 deletions docs/pom.xml
Expand Up @@ -24,8 +24,24 @@
<groovy.version>2.5.10</groovy.version>
</properties>
<build>
<sourceDirectory>src/main/asciidoc</sourceDirectory>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/asciidoc</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
Expand Down Expand Up @@ -75,12 +91,10 @@
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jsonSchema</artifactId>
<version>${jackson-module-jsonSchema.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
Expand Down Expand Up @@ -115,6 +129,19 @@
<executable>./build_adocs.sh</executable>
</configuration>
</execution>
<execution>
<id>generate-adoc-resources</id>
<phase>process-classes</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.springframework.cloud.contract.docs.Main</mainClass>
<arguments>
<argument>${maven.multiModuleProjectDirectory}/docs</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
Expand Down
185 changes: 185 additions & 0 deletions docs/src/main/asciidoc/_project-features-flows.adoc
Expand Up @@ -439,3 +439,188 @@ Contract.make {

The generated document (formatted in Asciidoc in this case) contains a formatted
contract. The location of this file would be `index/dsl-contract.adoc`.

[[features-graphql]]
=== GraphQL

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,attributes",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,attributes",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,attributes",role="primary"]
.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>
----

[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
contracts {
testMode = "EXPLICIT"
baseClassForTests = "com.example.BaseClass"
}
----
====

The base class would set up the applicatoin running on a random port.

====
[source,java,indent=0,subs="verbatim,attributes"]
.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,attributes"]
.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<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);

}
}

----
====
2 changes: 1 addition & 1 deletion docs/src/main/asciidoc/_project-features-stubrunner.adoc
Expand Up @@ -81,7 +81,7 @@ You can pick from the following options of acquiring stubs:
- Classpath-scanning solution that searches the classpath with a pattern to retrieve stubs
- Writing your own implementation of the `org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder` for full customization

The latter example is described in the <<advanced.adoc#customization-custom-stub-runner, Custom Stub Runner>> section.
The latter example is described in the <<advanced.html#customization-custom-stub-runner, Custom Stub Runner>> section.

[[features-stub-runner-downloading-stub]]
===== Downloading Stubs
Expand Down
26 changes: 13 additions & 13 deletions docs/src/main/asciidoc/documentation-overview.adoc
Expand Up @@ -111,18 +111,18 @@ link:docker-project.html[Docker]
Finally, we have a few topics for more advanced users:

* *Customizing the DSL:*
<<advanced.adoc#contract-dsl-customization, DSL Customization>> |
<<advanced.adoc#contract-dsl-extending-common-jar, Common JAR>> |
<<advanced.adoc#contract-dsl-test-dep, Test Dependency>> |
<<advanced.adoc#contract-dsl-plugin-dep, Plugin Dependency>> |
<<advanced.adoc#contract-dsl-referencing, Referencing the DSL>>
<<advanced.html#contract-dsl-customization, DSL Customization>> |
<<advanced.html#contract-dsl-extending-common-jar, Common JAR>> |
<<advanced.html#contract-dsl-test-dep, Test Dependency>> |
<<advanced.html#contract-dsl-plugin-dep, Plugin Dependency>> |
<<advanced.html#contract-dsl-referencing, Referencing the DSL>>
* *Customizing WireMock:*
<<advanced.adoc#customization-wiremock-extension, Extensions>> |
<<advanced.adoc#customization-wiremock-configuration, Configuration>>
<<advanced.html#customization-wiremock-extension, Extensions>> |
<<advanced.html#customization-wiremock-configuration, Configuration>>
* *Customizing {project-full-name}:*
<<advanced.adoc#contract-dsl-pluggable-architecture, Pluggable Architecture>> |
<<advanced.adoc#contract-dsl-custom-contract-converter, Contract Converter>> |
<<advanced.adoc#contract-dsl-custom-test-generator, Test Generator>> |
<<advanced.adoc#contract-dsl-custom-stub-generator, Stub Generator>> |
<<advanced.adoc#contract-dsl-custom-stub-runner, Stub Runner>> |
<<advanced.adoc#contract-dsl-custom-stub-downloader, Stub Downloader>>
<<advanced.html#contract-dsl-pluggable-architecture, Pluggable Architecture>> |
<<advanced.html#contract-dsl-custom-contract-converter, Contract Converter>> |
<<advanced.html#contract-dsl-custom-test-generator, Test Generator>> |
<<advanced.html#contract-dsl-custom-stub-generator, Stub Generator>> |
<<advanced.html#contract-dsl-custom-stub-runner, Stub Runner>> |
<<advanced.html#contract-dsl-custom-stub-downloader, Stub Downloader>>
2 changes: 1 addition & 1 deletion docs/src/main/asciidoc/project-features.adoc
Expand Up @@ -37,4 +37,4 @@ If you want to learn more about any of the classes discussed in this section, yo

If you are comfortable with {project-full-name}'s core features, you can continue on and read
about
<<advanced.adoc, {project-full-name}'s advanced features>>.
<<advanced.html, {project-full-name}'s advanced features>>.