Testing GraphQL APIs

Write calls in Playground then transform them into test scripts in Ruby

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.

Screenshot of GraphQL Playground for Trevor Blades’ Countries API
  1. Try out queries in GraphQL Playground.
  2. I also may do it in an HTTP graphic tool that offers more features.
  3. By then, I have a good idea of the query request I’m sending and what kind of response data I expect.
  4. 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.
  5. Run, verify and refine (typically, I do this using TestWise).

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
}
}

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.

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
end

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")
end

CREATE 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.

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
end

UPDATE 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
}
}
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")
end

DELETE a record

The final operation in this tutorial is DELETE.

GraphQL Query

mutation DeletePost($deletePostId: Int!) {
deletePost(id: $deletePostId) {
id
title
}
}
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")
end
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")

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store