Contract testing a GraphQL API
REST is dead, long live REST
GraphQL is being hailed by many as the new REST - it has type safety, a neat DSL and a great ecosystem. Perhaps best of all, it focuses on the needs of the client; consumers get the data they want, in the shape they want it - nothing more and nothing less.
I can now fetch data from my BFF via my React Component with caching, error handling and state management all taken care of for me, just like so:
import { Query } from "react-apollo";
import gql from "graphql-tag";
const ExchangeRates = () => (
<Query
query={gql`
{
rates(currency: "USD") {
currency
rate
}
}
`}
>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.rates.map(({ currency, rate }) => (
<div key={currency}>
<p>{`${currency}: ${rate}`}</p>
</div>
));
}}
</Query>
);
For some, however, this ability to define a schema and even generate client code, harkens back to the brittle and dark ages of WSDL, whereby clients are tightly coupled to their API implementation.
Can we have our cake and it it too?
Do you see this as a bad thing? WSDL absolutely has its issues, but it is a definition language. CDC is an approach, which can work with WSDL, Swagger, or any definition format or wire protocol. They are orthogonal concepts
— Matt Fellows (@matthewfellows) July 18, 2018
If you look under the covers, it turns out GraphQL is actually just a simple abstraction over HTTP, which means we can still test GraphQL as we do with regular RESTful APIs - including using contract testing! 🙌
GraphQL (mostly) follows just a few simple rules:
- Requests are made via an
HTTP POST
- GraphQL queries are sent as stringified JSON contained within a
query
property of the request - The response body is wrapped in the
data
sub-property, namespaced by the operation (Query or Mutation) that is being called, alongside anyerrors
for the operation.
Read more about the GraphQL specification here
Constructing a basic cURL for a simplistic hello
operation looks something like this:
curl -X POST \
-H 'content-type: application/json' \
-d '{ "query": "{ hello }" }' \
http://someapi/api
Whilst you can create this request using the usual Pact DSL, in our latest version of Pact JS (6.x.x
or @prerelease
) we have even created a GraphQLInteraction
interface to simplify creating the expectations - including variables support and matching rules:
const graphqlQuery = new GraphQLInteraction()
.uponReceiving("a hello request")
.withQuery(`{ hello(person: $person }`)
.withRequest({
path: "/graphql",
method: "POST",
})
.withVariables({
person: "Sally",
})
.willRespondWith({
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: {
data: {
hello: like("Hello Sally"),
},
},
});
So there it is, Pact + GraphQL: a match made in heaven.