Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Beginnings of a reusable GraphQL Ruby type testing toolkit.
# Assumptions
# 1. Your schema implements the Relay Query.node(id:) field, Schema.id_from_object, and Schema.object_from_id
# 2. The types you wish to test implement the Node interface
# 3. (In order to be compatible with Node) the object you test resolves to a single type
# 4. The object you wish to test has a unique identifier
# Maybe we can remove 3. by making use of an override in context?
# Since Schema.resolve_type takes a context object, perhaps if you must test an object that is
# presented via multiple types in the schema, you can force the decision via context. I have not
# needed this yet, so not implemented, but seems straightforward.
# Maybe we can avoid the need for all this Relay business by re-opening the schema at test runtime and just
# creating the resolvers we need on Query to run the test. This would remove the need for 1. and 2.
# I suspect that all object type field tests will end up being of the format `myAssociation { id __typename }`.
# Perhaps we can use schema introspection to know when a field is an object that implements `id`, and do this
# ourselves. This seems a little too magical to me I think. Like, does this make sense to you?:
# user = User.create(friends: { User.create })
# result ="friends", user)
# expect(result).to eq(["1", "2", "3"]) # << How did we know to compare IDs here?
module GraphQL
module Testing
class TypeTester
# GraphQL::Testing::TypeTester provides a convenience API for testing field resolvers via the
# Relay `Query.node(id:)` field. To use it, extend and specify the schema to be tested:
# class MySchemaTypeTester < GraphQL::Testing::TypeTester
# schema MySchema
# end
# Usage:
# @example Scalar types
# user = User.create(email: "")
# result =, "email")
# expect(result).to eq("")
# @example Scalar types, with context
# user = User.create(email: "")
# result = {user: user}).resolve_field(user, "email")
# expect(result).to eq("")
# @example Scalar type, with variables:
# user = User.create(email: "")
# result =, "email(only: DOMAIN)")
# expect(result).to eq("")
# @example Object types
# user = User.create(friends: { User.create })
# result =, "friends { id }")
# expect(result).to eq(["1", "2", "3"])
attr_reader :context
class << self
def schema(schema = nil)
@schema = schema if schema
def initialize(context: {}, account: nil)
context[:account] ||= account
@context = context
# Executes a query fetching the given fields. Infers the type that the fields belong using
# `GraphQL::Schema#resolve_type(object, context)`.
# @param object the object used to infer the type, and passed into the resolver for the `field`.
# @param field_and_subfields [String] the field (with subfields for object types) to resolve
# @param context [Hash] field specific context, merged into shared context from `#initialize`
# @return the result of querying for the given field (and optional subfields)
def resolve_field(object, field_and_subfields, context: {})
account ||= context[:account]
field_context = shared_context.merge(context)
type = resolve_type_from_object(object, context)
field_and_subfields = field_and_subfields.camelize(:lower)
# If the user supplied an object field, extract the top level field name
# E.g. "photos { account { id } }" => "photos"
field = field_and_subfields.match(/\A\w+/)[0]
assert_field_exists!(type, field)
query = node_query(type, field_and_subfields)
variables = node_variables(object, type, context)
result = run_graphql_query(query, variables: variables, context: field_context)
if result.errors
def schema
def assert_absract_class_extended!
if self === GraphQL::Testing::TypeTester
raise(NotImplementedError, "GraphQL::Testing::TypeTester is an abstract class, extend and set #schema to use")
def resolve_type_from_object(object, context)
if type = schema.resolve_type(object, context)
return type
raise"Could not find infer Type for #{object}")
def assert_field_exists!(type, field)
rescue KeyError => message
raise"Could not find Field #{field}\n\n#{message}")
def raise_query_errors(errors)
error =<<~EOT, errors)
GraphQL Query Error!
If you were expecting an error, change your spec to:
expect { result }.to raise_error(GraphQL::QueryError)
raise error
def node_query(type, field)
query($id: ID!) {
# Use the Relay node API to fetch the instance by ID from the root node.
node(id: $id) {
... on #{type} {
def node_variables(object, type, context)
{id: schema.id_from_object(object, type, context).to_s}
def shared_context
def run_graphql_query(query, variables: {}, context: {})
account = context[:account]
context = context.reverse_merge(
current_user: account,
account: account,
result = DirectorySchema.execute(query, context: context, variables: variables)
result =
Copy link

jgrau commented Jul 26, 2021

@bessey I’ve been struggling to find a good way to test my graphql-ruby application and found this gist when looking for inspiration. I’m wondering if you pursued this way of testing any further or if you abandoned it… no matter what: thanks for this gist - it’s been inspirational. :)

Copy link

bessey commented Aug 3, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment