GraphQL
Commercial
- GraphQL
- Introduction
- What Can You Achieve with Specmatic’s GraphQL Support?
- Quick start with sample projects
- Starting a stub server
- Stubbing out specific values using example data
- HTTP Headers
- GraphQL Variables
- Delayed responses
- Dynamic Field Selection from Example Responses
- Multi-Field Queries
- Aliases
- Errors
- GraphQL Scalar Types
- Running contract tests
- Using your GraphQL files as your API Contracts from Central Contract Repository
- Introspection
- GraphiQL GUI
Introduction
Specmatic supports service virtualization, contract testing and backward compatibility for GraphQL APIs based on GraphQL SDL (Schema Definition Language) files, similar to its support for REST (OpenAPI).
What Can You Achieve with Specmatic’s GraphQL Support?
With Specmatic GraphQL support you will be to leverage your GraphQL SDL files as executable contracts.
- Intelligent Service Virtualization: Stub out GraphQL services for testing and development
- Contract Testing: Validate requests and responses against your GraphQL SDL files
- Backward Compatibility Checks: Compare two versions of your GraphQL SDL files to identify breaking changes without writing any code
- Central Contract Repo: Store your GraphQL SDL files in central Git repo which will serve as single source of truth for both providers and consumers
- API resiliency : Generate negative and edge cases to verify the resiliency of your API implementation based on your GraphQL SDL files.
Quick start with sample projects
Here are sample projects in different languages and frameworks that demonstrate below features in various languages and scenarios. Refer to the latest projects here:
README of each of these projects include
- detailed animated architecture diagram
- instructions on how to start stub server using graphql spec with custom examples, simulating delays, errors etc.
- instructions on how to isolate the System Under Test when running contract tests
- example CI workflow setups using GitHub Actions
Starting a stub server
To start a stub server using Specmatic, you can use the following steps:
Step 1: Create a graphql SDL file
Create a GraphQL SDL file named proudct-api.graphql as shown below.
enum ProductType {
gadget
book
food
other
}
type Query {
findAvailableProducts(type: ProductType!, pageSize: Int): [Product]
}
type Product {
id: ID!
name: String!
inventory: Int!
type: ProductType!
}
Step 2: Start the stub server
Run the following command to start the GraphQL stub:
docker run -v "$PWD:/sandbox" -p 9000:9000 \
specmatic/specmatic-graphql virtualize /sandbox/product-api.graphql
That’s it! You now have a GraphQL stub server running on localhost:9000/graphql serving the schema defined in product-api.graphql.
Step 3: Make a request to the stub server
Let’s try sending GraphQL queries to this server.
Use the following curl command to make a request:
curl -X POST http://localhost:9000/graphql \
-H "Content-Type: application/json" \
-d '{ "query": "query { findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type } }" }'
You will receive a response with a random product ID, name, inventory, and type as shown below. The values are generated based on the GraphQL SDL file product-api.graphql you provided in the Step 2.
{
"data": {
"findAvailableProducts": [
{
"id": "YOTHQ",
"name": "HAXVO",
"inventory": 536,
"type": "gadget"
}
]
}
}
Stubbing out specific values using example data
Above, we saw Specmatic generating random output based on the provided product-api.graphql spec. Instead, in order to receive specific example values, you can create an example YAML files containing specific data for findAvailableProducts query.
Let’s see how to do that.
Step 1. Create an examples file
Create product-api_examples/findAvailableProducts.yaml with following content
request:
body: |
query {
findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type }
}
response: [
{
"id": "10",
"name": "The Almanac",
"inventory": 10,
"type": "book"
},
{
"id": "20",
"name": "iPhone",
"inventory": 15,
"type": "gadget"
}
]
Note: Keep the file product-api.graphql and directory product-api_examples at same level.
Step 2: Start the stub server
Run the following command to start the GraphQL stub:
docker run -v "$PWD:/spec" -p 9000:9000 \
specmatic/specmatic-graphql virtualize /spec/product-api.graphql
Step 3: Make a request to the stub server
Let’s try sending the same request as earlier.
curl -X POST http://localhost:9000/graphql \
-H "Content-Type: application/json" \
-d '{ "query": "query { findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type } }" }'
This time though, you will receive a response with the specified example defined in Step 1.
{
"data": {
"findAvailableProducts": [
{
"id": "10",
"name": "The Almanac",
"inventory": 10,
"type": "book"
},
{
"id": "20",
"name": "iPhone",
"inventory": 15,
"type": "gadget"
}
]
}
}
Providing a custom examples directory
By convention, Specmatic automatically loads all examples defined for the GraphQL SDL file, say <graphql_sdl_filename>.graphql, from a colocated directory called <graphql_sdl_filename>_examples. However, in case your examples are in a different directory, you can pass it as an argument programmatically or through CLI args while running tests or service virtualization.
Example file format
Let us now take deeper look at the external example format.
- At the top level we have two YAML root nodes called
requestandresponse requestcan hold the following keys:body: this can contain eitherquerys ormututations with exact values where necessaryheaders: you can add yourHTTPheaders here
responseholds responses with JSON syntax for readability, syntax highlighting and also as an aid to copy and paste of real responses from actual application logs etc.
HTTP Headers
Although GraphQL SDL files do not support HTTP request headers, you may directly add them to your Specmatic example files in httpHeaders under the request key, as seen in the example yaml below. The headers will be leveraged if present both by the contract tests and service virtualization.
request:
httpHeaders:
X-region: north-west
body: |
query {
findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type }
}
response: [
{
"id": "10",
"name": "The Almanac",
"inventory": 10,
"type": "book"
},
{
"id": "20",
"name": "iPhone",
"inventory": 15,
"type": "gadget"
}
]
GraphQL Variables
Specmatic supports usage of GraphQL variables seamlessly. You only need to make sure that the externalised example is structured such that it uses the actual field values inline instead of variables in the query. Here is an example.
Say suppose, below request is that is being sent by your GraphQL Consumer to Specmatic GraphQL service virtualization server.
{
"operationName": "FindAvailableProducts",
"variables": {
"type": "gadget",
"pageSize": 10
},
"query": "query FindAvailableProducts($type: ProductType!, $pageSize: Int!) { findAvailableProducts(type: $type, pageSize: $pageSize) { id name inventory type } }"
}
As you can see, the above request from GraphQL consumer includes a variable called $pageSize. However, in our example we will not be using it, instead we will be using the actual value (pageSize: 10) to match a request that comes with that value.
request:
body: |
findAvailableProducts(type: "gadget", pageSize: 10) {
id
name
inventory
type
}
response: [
{
"id": "10",
"name": "The Almanac",
"inventory": 10,
"type": "book"
},
{
"id": "20",
"name": "iPhone",
"inventory": 15,
"type": "gadget"
}
]
Note: It is recommended to specify the type of the variable e.g. $pageSize: Int!. If the type is not specified explicitly you may face some issues since Specmatic will implicitly cast the variable to a certain type which may be invalid sometimes.
Delayed responses
To simulate delay in the response, you can use the delay-in-milliseconds key in your example YAML file. For example:
request:
httpHeaders:
X-region: north-west
body: |
query {
findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type }
}
response: [
{
"id": "10",
"name": "The Almanac",
"inventory": 10,
"type": "book"
},
{
"id": "20",
"name": "iPhone",
"inventory": 15,
"type": "gadget"
}
]
delay-in-milliseconds: 5000
This will introduce a 5-second delay before the response is sent back to the client.
Dynamic Field Selection from Example Responses
When running a GraphQL stub using Specmatic, you can provide an example query that includes all possible fields and sub-selections. Specmatic uses this example to generate the stub response.
If a request is made using the exact query provided in the example, Specmatic will return the full response as defined. However, if a request is made with a subset of the fields specified in the original example, Specmatic will automatically tailor the response to include only those requested fields.
This means that a comprehensive example (an “uber example”) covering all fields can be reused by Specmatic to generate responses for queries that request only a subset of those fields.
Here are some simple steps to try this out:
1. Create a GraphQL SDL file named product-api.graphql with the following content:
schema {
query: Query
mutation: Mutation
}
enum ProductType {
gadget
book
food
other
}
type Query {
findAvailableProducts(type: ProductType!, pageSize: Int): [Product]
}
type Product {
id: ID!
name: String!
inventory: Int!
type: ProductType!
}
2. Create a folder named examples.
3. Create an example file named find_available_gadgets.yaml in the examples folder with the following content:
request:
body: |
query {
findAvailableProducts(type: gadget, pageSize: 10) {
id
name
inventory
type
}
}
response: [
{
"id": "10",
"name": "The Almanac",
"inventory": 10,
"type": "book"
},
{
"id": "20",
"name": "iPhone",
"inventory": 15,
"type": "gadget"
}
]
4. Create a specmatic.yaml file with the following content in the root folder where product-api.graphql file is present:
contract_repositories:
- type: filesystem
consumes:
- product-api.graphql
5. Run the following command to start the GraphQL stub on localhost:9000/graphql:
docker run -v "$PWD/product-api.graphql:/usr/src/app/product-api.graphql" \
-v "$PWD/examples:/usr/src/app/examples" \
-v "$PWD/specmatic.yaml:/usr/src/app/specmatic.yaml" \
-p 9000:9000 specmatic/specmatic-graphql virtualize --port=9000 --examples=examples
6. Make a request to query all the sub-selected fields specified in the example. You will get the exact response specified in the example. Use the following curl command to make this request:
curl -X POST http://localhost:9000/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type } }"
}'
Expected Response:
{
"data": {
"findAvailableProducts": [
{
"id": "10",
"name": "The Almanac",
"inventory": 10,
"type": "book"
},
{
"id": "20",
"name": "iPhone",
"inventory": 15,
"type": "gadget"
}
]
}
}
7. Make a request to query only a subset of the sub-selected fields specified in the example. You will get a response with only those sub-selected fields with values picked up from the response. Use the following curl command to make this request:
curl -X POST http://localhost:9000/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { findAvailableProducts(type: gadget, pageSize: 10) { id name } }"
}'
Expected Response:
{
"data": {
"findAvailableProducts": [
{
"id": "10",
"name": "The Almanac"
},
{
"id": "20",
"name": "iPhone"
}
]
}
}
This setup allows you to test how Specmatic reuses the example provided, adapting the response to the requested fields.
Multi-Field Queries
The Specmatic GraphQL stub server supports multi-field queries, allowing you to send a single request with multiple queries and receive a consolidated response. This feature is useful when you want to retrieve data from different queries in a single API call. Additionally, multi-field queries with variables are supported, making it flexible for dynamic requests.
To showcase this, let’s reuse the folder structure established in the previous section on dynamic field selection.
Steps to Try Out Multi-Field Queries
- Create the GraphQL SDL File: Create a file named
product-api.graphqlwith the following schema:schema { query: Query mutation: Mutation } enum ProductType { gadget book food other } type Query { findAvailableProducts(type: ProductType!, pageSize: Int): [Product] productById(id: ID!): Product } type Product { id: ID! name: String! inventory: Int! type: ProductType! } -
Set Up Example Files: In the
examplesfolder, create two example files:find_available_gadgets.yaml:request: body: | query { findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type } } response: [ { "id": "10", "name": "The Almanac", "inventory": 10, "type": "book" }, { "id": "20", "name": "iPhone", "inventory": 15, "type": "gadget" } ]product_by_id.yaml:request: body: | query { productById(id: "10") { id name type } } response: { "id": "10", "name": "The Almanac", "type": "book" }
-
Create
specmatic.yamlFile: Add the followingspecmatic.yamlfile to the root folder:version: 2 contracts: - consumes: - product-api.graphql - Run the GraphQL Stub:
docker run -v "$PWD/product-api.graphql:/usr/src/app/product-api.graphql" \ -v "$PWD/examples:/usr/src/app/examples" \ -v "$PWD/specmatic.yaml:/usr/src/app/specmatic.yaml" \ -p 9000:9000 specmatic/specmatic-graphql virtualize --port=9000 --examples=examples - Send a Multi-Query Request:
Use the following
curlcommand to make a multi-query request:curl -X POST http://localhost:9000/graphql \ -H "Content-Type: application/json" \ -d '{ "query": "query { findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type } productById(id: \"10\") { id name type } }" }'Expected Response:
{ "data": { "findAvailableProducts": [ { "id": "10", "name": "The Almanac", "inventory": 10, "type": "book" }, { "id": "20", "name": "iPhone", "inventory": 15, "type": "gadget" } ], "productById": { "id": "10", "name": "The Almanac", "type": "book" } } }
Here’s an example of a multi-field query using variables. This demonstrates how Specmatic can handle a single request with multiple queries, where each query may use variables.
-
Send a Multi-Field Query Request with Variables: Use the following
curlcommand:curl -X POST http://localhost:9000/graphql \ -H "Content-Type: application/json" \ -d '{ "query": "query($type: ProductType!, $pageSize: Int, $id: ID!) { findAvailableProducts(type: $type, pageSize: $pageSize) { id name inventory type } productById(id: $id) { id name type } }", "variables": { "type": "gadget", "pageSize": 10, "id": "10" } }'Expected Response:
{ "data": { "findAvailableProducts": [ { "id": "10", "name": "The Almanac", "inventory": 10, "type": "book" }, { "id": "20", "name": "iPhone", "inventory": 15, "type": "gadget" } ], "productById": { "id": "10", "name": "The Almanac", "type": "book" } } }
In this example:
- The variable
$typeis used to specify theProductTypeforfindAvailableProducts. $pageSizecontrols the number of products returned.$idis used to fetch a specific product by ID in theproductByIdquery.
This request showcases how Specmatic’s GraphQL stub server can process multi-query requests with variables, offering flexibility and efficiency in response generation.
This example demonstrates how Specmatic processes multiple queries in a single request and returns the expected responses for each query. You can adapt this setup for various use cases, leveraging the existing folder structure for organizing examples.
Aliases
GraphQL allows you to use aliases to fetch the same field multiple times with different arguments, or to organize the response structure for clarity. Specmatic supports GraphQL aliases.
Aliases are useful when you want to query the same field with different parameters, or when you want to rename fields in the response for easier consumption.
Let’s see how it works using the same product-related GraphQL spec and example files described above.
Steps to Try Out Multi-Field Requests
- Create the GraphQL SDL File: Use the same
product-api.graphqlschema as in the Multi-Query Requests section:schema { query: Query mutation: Mutation } enum ProductType { gadget book food other } type Query { findAvailableProducts(type: ProductType!, pageSize: Int): [Product] productById(id: ID!): Product } type Product { id: ID! name: String! inventory: Int! type: ProductType! } - Set Up Example Files: In the
examplesfolder, use the same example files as in the Multi-Query Requests section:find_available_gadgets.yamlproduct_by_id.yaml
-
Create
specmatic.yamlFile: Use the same configuration as before:contract_repositories: - type: filesystem consumes: - product-api.graphql - Run the GraphQL Stub:
docker run -v "$PWD/product-api.graphql:/usr/src/app/product-api.graphql" \ -v "$PWD/examples:/usr/src/app/examples" \ -v "$PWD/specmatic.yaml:/usr/src/app/specmatic.yaml" \ -p 9000:9000 specmatic/specmatic-graphql virtualize --port=9000 --examples=examples - Send a Multi-Field Request Using Aliases:
You can use aliases to fetch the same field multiple times with different arguments. For example:
query ProductData { firstProduct: productById(id: "10") { id name type } availableGadgets: findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type } }To try this out, send the following
curlrequest:curl -X POST http://localhost:9000/graphql \ -H "Content-Type: application/json" \ -d '{ "query": "query ProductData { firstProduct: productById(id: \"10\") { id name type } availableGadgets: findAvailableProducts(type: gadget, pageSize: 10) { id name inventory type } }" }'Expected Response:
{ "data": { "firstProduct": { "id": "10", "name": "The Almanac", "type": "book" }, "availableGadgets": [ { "id": "10", "name": "The Almanac", "inventory": 10, "type": "book" }, { "id": "20", "name": "iPhone", "inventory": 15, "type": "gadget" } ] } }
This demonstrates how Specmatic supports multi-field requests with aliases, allowing you to fetch the same field multiple times with different arguments and organize your response structure as needed.
Errors
You can now mock out errors in examples, like this:
request:
body: |
query {
book(id: "10") {
id
name
type
}
}
response:
body:
id: "10"
name: The Almanac
type: null
errors: [
{
"message": "Address service is unavailable",
"path": [ "book", "type" ]
}
]
The error schema is validated as per the GraphQL specification. The path is additionally validated to ensure it points to a valid field in the GraphQL schema.
GraphQL Scalar Types
In GraphQL, you can define custom scalar types to handle specialized data, such as dates or monetary values, that require specific serialization and deserialization logic. For example:
schema {
query: Query
mutation: Mutation
}
scalar Date
type Offer {
offerCode: String!
validUntil: Date!
}
type Query {
findOffersForDate(date: Date!): [Offer]!
}
In this schema, Date is a custom scalar. While GraphQL doesn’t provide details on how this scalar is serialized or deserialized, your application code defines the logic.
When testing queries like findOffersForDate using Specmatic, the tool doesn’t know how to correctly handle the Date scalar and might pass an incorrect value, such as a random string, leading to test failures.
Specmatic supports the following custom scalar types out-of-the box:
- Date, DateTime
- Long, Double, BigDecimal
For other data types, you can provide externalized examples that specify valid inputs for custom scalars like.
Running contract tests
To run contract test:
docker run --network host -v "$(pwd)/specmatic.yml:/usr/src/app/specmatic.yml" -v "$(pwd)/build/reports/specmatic:/usr/src/app/build/reports/specmatic" -e SPECMATIC_GENERATIVE_TESTS=true specmatic/specmatic-graphql test --port=8080
This command mounts your specmatic.yaml file and runs tests against a service running on port 8080 by generating GraphQL requests based on the GraphQL SDL files listed under provides section along with examples if any provided in the colocated directory named <GraphQL SDL file without extension>_examples.
Also, it mounts the build artifacts from the docker container onto your local machine once the test run is completed.
Using your GraphQL files as your API Contracts from Central Contract Repository
- Store your GraphQL SDL files in a central repository for easy access and version control.
- Create a
specmatic.yamlfile in the root of your project to reference these contracts. Here’s an example:
version: 2
contracts:
- git:
url: https://github.com/specmatic/specmatic-order-contracts.git
provides:
- io/specmatic/examples/store/graphql/products_bff.graphqls
consumes:
- io/specmatic/examples/store/openapi/api_order_v3.yaml
Make sure to update the repository, provides and consumes sections to reflect your actual contract repository and .graphqls file locations.
Introspection
Specmatic supports GraphQL introspection queries, allowing clients to query the GraphQL schema. This is useful for tools such as GraphQL GUI clients that read the GraphQL schema using introspection and provide features like autocompletion and documentation.
GraphiQL GUI
Specmatic ships with the GraphiQL GUI, an in-browser GraphQL IDE, that you can use to explore and test your GraphQL APIs. You can access it at http://localhost:9000/graphiql when your Specmatic GraphQL stub server is running.