Besides testing schema structure, you should also test your GraphQL system’s behavior. There are really a few levels to this:
When it comes to how your application behaves, you should lean on unit tests which exercise application primitives directly. For example, if postings require a title and a body, you should write a test for Post
which asserts that invalid Post
s fail to save. Several other components of the application may be tested this way:
By testing these (and other) application-level behaviors without GraphQL, you can reduce the overhead of your test suite and simplify your testing scenarios.
After building your application, you give it an interface so that people (or other software) can interact with it. Sometimes the interface is a website, other times it’s a GraphQL API. The interface has transport-specific primitives that map to your application’s primitives. For example, a React app might have components that correspond to Post
, Comment
, and a Moderation
operation. (These components might even be context-specific, like ThreadComment
or DraftPost
.) Similarly, a GraphQL interface has types and fields that correspond to the underlying application primitives (like Post
and Comment
types, a Post.isDraft
field, or a ModerateComment
mutation).
The best way to test a GraphQL interface is with integration tests which run the whole GraphQL system (using MySchema.execute(...)
). By using an integration test, you can be sure that all of GraphQL-Ruby’s internal systems are engaged (validation, analysis, authorization, data loading, response type-checking, etc.).
An basic integration test might look like:
it "loads posts by ID" do
query_string = <<-GRAPHQL
query($id: ID!){
node(id: $id) {
... on Post {
title
id
isDraft
comments(first: 5) {
nodes {
body
}
}
}
}
}
GRAPHQL
post = create(:post_with_comments, title: "My Cool Thoughts")
post_id = MySchema.id_from_object(post, Types::Post, {})
result = MySchema.execute(query_string, variables: { id: post_id })
post_result = result["data"]["node"]
# Make sure the query worked
assert_equal post_id, post_result["id"]
assert_equal "My Cool Thoughts", post_result["title"]
end
To make sure that different parts of the system are properly engaged, you can add integration tests for specific scenarios, too. For example, you could add a test to make sure that data is hidden from some users:
it "doesn't show draft posts to anyone except their author" do
author = create(:user)
non_author = create(:non_user)
draft_post = create(:post, draft: true, author: author)
query_string = <<-GRAPHQL
query($id: ID!) {
node(id: $id) {
... on Post {
isDraft
}
}
}
GRAPHQL
post_id = MySchema.id_from_object(post, Types::Post, {})
# Authors can see their drafts:
author_result = MySchema.execute(query_string, context: { viewer: author }, variables: { id: post_id })
assert_equal true, author_result["data"]["node"]["isDraft"]
# Other users can't see others' drafts
non_author_result = MySchema.execute(query_string, context: { viewer: non_author }, variables: { id: post_id })
assert_nil author_result["data"]["node"]
end
This test engages the underlying authorization and business logic, and provides a sanity check at the GraphQL interface layer.
GraphQL is usually served over HTTP. You probably want tests that make sure that HTTP inputs are correctly prepared for GraphQL. For example, you might test that:
context[:viewer]
In Rails, you might use a functional test for this, for example:
it "loads user token into the viewer" do
query_string = "{ viewer { username } }"
post graphql_path, params: { query: query_string }
json_response = JSON.parse(@response.body)
assert_nil json_response["data"]["viewer"], "Unauthenticated requests have no viewer"
# This time, add some authentication to the HTTP request
user = create(:user)
post graphql_path,
params: { query: query_string },
headers: { "Authorization" => "Bearer #{user.auth_token}" }
json_response = JSON.parse(@response.body)
assert_equal user.username, json_response["data"]["viewer"], "Authenticated requests load the viewer"
end