RSpec GraphQL Integration Testing
This RSpec plugin simplifies integration tests for GraphQL.
Introduction
This plugin mainly consists of a matcher called match_graphql_response
that executes a query or mutation against the defined schema and checks the response against a JSON response.
Idea behind this plugin
The problem
When writing GraphQL integration tests, you write your query in a multiline string, get the response, parse it (probably with a helper) and write some expectations, maybe even expecting a whole multi-dimensional Hash
. This could then look something like this:
RSpec.describe "Query.currentUser" do
subject(:query_result) { MySchema.execute(query, context: context).as_json }
let(:user) { create(:user) }
let(:context) { { current_user: user } }
let(:query) { <<~GRAPHQL }
query {
currentUser {
id
email
}
}
GRAPHQL
let(:expected_result) do
{ "data" => { "currentUser" => { "id" => user.id.to_s, "email" => user.email } } }.as_json
end
it "returns the current user" do
expect(query_result).to eq(expected_result)
end
end
For small queries, this is fine. But for big queries (and hence, big responses) this gets unhandy very fast. This is subjective of course ;)
Another issue is that we can't leverage the GraphQL language server while writing/maintaining these integration tests.
The solution provided by this gem
This gem tries to improve this situation by moving the query and the response in their own files with a proper file type. This way, the integration test files are smaller and can focus on mocking data/instances. Also, the GraphQL language server will give you autocompletion/linting in your GraphQL files (if you've set up your editor for it).
The simple integration test from above then looks like this:
current_user_spec.rb
RSpec.describe "Query.currentUser" do
let(:user) { create(:user) }
let(:context) { { current_user: user } }
let(:response_variables) { { user_id: user.id, user_email: user.email } }
it { is_expected.to match_graphql_response }
end
current_user.graphql
query {
currentUser {
id
email
}
}
current_user.json
{
"data": {
"currentUser": {
"id": "{{user_id}}",
"email": "{{user_email}}"
}
}
}
TODO: Think about using a templating language for the response
Configuration
You can have a look at the RSpec GraphQL integration configuration of this gem to get started.
Schema class
You need to define the schema class that is used to execute the queries against. You can also define a main schema, but overwrite this for specific tests with let(:schema_class_overwrite) { MyOtherSchema }
.
require "rspec/graphql_integration"
RSpec.configure do |config|
# ...
config.graphql_schema_class = MySchema
# ...
end
Files in folder
The default behavior for the request and response files is that they are named like the test file but instead of _spec.rb
they end with .graphql
and .json
. If you want the files to be in a folder named like the test file, activate this option. Then the matcher will look for <test_file_without_spec.rb>/(request.graphql|response.json)
instead of <test_file_without_spec.rb>.(graphql|json)
.
require "rspec/graphql_integration"
RSpec.configure do |config|
# ...
config.graphql_put_files_in_folder = true
# ...
end
Spec type from file location
Like with the RSpec::Rails
, this gem can infer the spec type (graphql
) from the spec file location. If you want this to happen, you have to enable this explicitly:
require "rspec/graphql_integration"
RSpec.configure do |config|
# ...
config.infer_graphql_spec_type_from_file_location!
# ...
end
Special variables
A lot of things can be adjusted per test by setting special variables. You need to set them with let
:
let(:context) { { current_user: user } }
Here is an overview of what can be set:
Variable | Description | Example test |
---|---|---|
test_file_overwrite |
Overwrites the location of the test file, if the automatic test file discovery is not working. This should normally not be necessary. | link |
schema_class_overwrite |
Overwrites the schema class that is used for the test. This is only necessary if you have more than one GraphQL schema in your codebase. | link |
request_file_overwrite |
Overwrites the filename of the request file, in case you want to reuse request files or put them inside a folder. | link |
response_file_overwrite |
Overwrites the filename of the response file, in case you want to reuse response files or put them inside a folder. | link |
context |
The GraphQL context that is passed to the GraphQL schema. | link |
request_variables |
Sets the variables that are passed into the GraphQL schema execution. This is necessary if you use variables inside your queries/mutations. | link |
response_variables |
Sets the variables that are used by the simple response template engine. | link |
Development
Useful commands are defined in the Justfile
and can be listed with just
.
License
MIT