Testing GraphQL APIs
Write calls in GraphQL Playground, then transform them into test scripts in Ruby.
GraphQL is an open-source data query and manipulation language for APIs and a runtime for fulfilling queries with existing data. It stores data in a graph-like structure.
This article will go through some common GraphQL API tests.
How I write GraphQL tests:
Most GraphQL endpoints provide a convenient and user-friendly interface (called Playground) to try out queries: you can type requests and get response data on the right. You can also view the schema there.

I follow the steps:
Try out queries in GraphQL Playground.
I also may do it in an HTTP graphic tool that offers more features.
By then, I have a good idea of the query request I’m sending and what kind of response data I expect.
Write an automated test in RSpec.
Invoking the GraphQL endpoint code is common. The majority of the effort is to parse response data and perform assertions. Ruby is a great language for that.Run, verify and refine (typically, I do this using TestWise).
When my test suite reaches a certain size, I run them in a Continuous Testing server, such as BuildWise.
I will use Trevor Blade’s public Countries API (endpoint: https://countries.trevorblades.com ) for some of the exercises in this article.
LIST all records
GraphQL Query
query is a GraphQL command to find matching data (list). The example below gets countries and returns the code and name back.
query {
countries {
code
name
}
}You can run this query in the GraphQL Playground, like below:
The above retrieves the fields codeand name from the countries.
In HTTP GUI client
You can also run GraphQL queries in a GUI HTTP client, such as Paw or Postman.
CURL Equivalent
For hardcore testers, you can also do it from the command line with CURL.
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{ "query": "{ countries { code name } } " }' \
https://countries.trevorblades.com/In Automated Test (Ruby)
From the above three ways, we have the request and response data. But, no testing yet.
I develop and run the test script in TestWise, a next-generational functional testing IDE.
Below is a complete automated test in RSpec.
require "httpclient"
require "net/http"
require "uri"
require "json"
describe "Graphql" do
it "List all continents" do
uri = URI.parse("https://countries.trevorblades.com/")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/json"
request.body = JSON.dump({
"query" => "{ countries { code name } } ",
})
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
expect(response.code).to eq("200") # call successful
expect(response.body).to include("Antarctica")
json_response_data = JSON.parse(response.body)
expect(json_response_data["data"]["countries"].count).to eq(250)
end
endAs you can see, after sending request and getting the data back in response we do assertions (in Ruby).
READ a specific record
GraphQL Query
query {
continent(code: "EU") {
code
name
countries {
name
emoji
}
}
}In RSpec Test
it "Get a particular continent" do
uri = URI.parse("https://countries.trevorblades.com/")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/json"
request.body = JSON.dump({
"query" => '{ continent(code: "EU") { code name countries { name emoji } } }',
})
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
expect(response.code).to eq("200")
expect(response.body).to include("Europe")
expect(response.body).not_to include("America")
expect(response.body).to include("Belgium")
endCREATE a new record
Most public endpoints are read-only, which is understandable. To demonstrate mutations, we must set up somewhere we can freely mutate ourselves.
I recommend using Prisma’s Users demo on Github. Follow the instructions in the ReadMe file (linked here) to download and run the sample server on http://localhost:4000.
GraphQL Query
mutation {
signupUser(data: { name: "P Sherman", email: "psher@email.com" }) {
id
}
}curl --request POST \
--header 'content-type: application/json' \
--url http://localhost:4000/ \
--data '{"query":"mutation {\n signupUser(data: { \n name: \"P Sherman\", email: \"psher@email.com\" }) {\n id\n }\n}","variables":{}}'In RSpec Test
it "Sign up a new user" do
uri = URI.parse("http://localhost:4000/")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/json"
request.body = JSON.dump({
"query" => "mutation {
signupUser(data: {
name: \"#{Faker::Name.name}\", email: \"#{Faker::Internet.email}\" }) {
id
}
}",
"variables" => "{}",
})
puts "request body: #{request.body}"
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
expect(response.code).to eq("200")
puts response.body
endUPDATE a record
For the update example, let’s update the status of a post from Published to Unpublished. I’ll use the existing sample post (id = 1):
// Result of reading Post data where id = 1
{
"data": {
"postById": {
"id": 1,
"title": "Join the Prisma Slack",
"content": "https://slack.prisma.io",
"published": true
}
}
}GraphQL Query
There is an existing mutation to toggle the published field in the schema:
mutation TogglePublishPost($togglePublishPostId: Int!) {
togglePublishPost(id: $togglePublishPostId) {
id
published
}
}This uses the variable togglePublishPostId to specify which post to update.
variables {
"togglePublishPostId": 1
}In RSpec Test
it "Toggle Published status" do
uri = URI.parse("http://localhost:4000/")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/json"
request.body = JSON.dump({
"query" => "mutation TogglePublishPost($togglePublishPostId: Int!) {
togglePublishPost(id: $togglePublishPostId) {
id
published
}
}",
"variables" => "{ \"togglePublishPostId\": 1 }",
})
puts "request body: #{request.body}"
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
expect(response.code).to eq("200")
puts response.body
expect(response.body).to include("false")
endNote: Because the above operation is
Toggle, notSet, if you run the above test twice, it will pass once and fail once. Please see my previous article on how to stabilise API tests.
DELETE a record
The final operation in this tutorial is DELETE.
GraphQL Query
mutation DeletePost($deletePostId: Int!) {
deletePost(id: $deletePostId) {
id
title
}
}Again, this uses the variable deletePostId to specify which Post to delete.
variables {
"deletePostId": 2
}In RSpec Test
it "Delete a post" do
uri = URI.parse("http://localhost:4000/")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/json"
request.body = JSON.dump({
"query" => "query PostById($postByIdId: Int) {
postById(id: $postByIdId) {
id
title
content
published
}
}}",
"variables" => "{ \"postByIdId\": 2 }",
})
puts "request body: #{request.body}"
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
expect(response.code).to eq("200")
endUsing assertion, we can verify that the post was deleted successfully with READ. We are expecting a null value to be returned for that post Id:
request.body = JSON.dump({
"query" => "query PostById($postByIdId: Int) { postById(id: $postByIdId) { id title content published } }",
"variables" => "{ \"postByIdId\": 2 }",
})
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
expect(response.code).to eq("200")
expect(response.body).to include("null")










