How to test a GraphQL schema with graphql-ruby?
Asked Answered
L

3

5

My goal is to test the types of my GraphQL schema in ruby, I'm using the graphql-ruby gem.

I couldn't find any best practice for this, so I'm wondering what's the best way to test the fields and types of a Schema.

The gem recommends against testing the Schema directly http://graphql-ruby.org/schema/testing.html but I still find valuable to be able to know when the schema changes unexpectedly.

Having a type like this:

module Types
  class DeskType < GraphQL::Schema::Object
    field :id, ID, 'Id of this Desk', null: false
    field :location, String, 'Location of the Desk', null: false
    field :custom_id, String, 'Human-readable unique identifier for this desk', null: false
  end
end

My first approach has been to use the fields hash in the GraphQL::Schema::Object type, for example:

Types::DeskType.fields['location'].type.to_s => 'String!'

Creating an RSpec matcher, I could come up with tests that look like this:

RSpec.describe Types::DeskType do
  it 'has the expected schema fields' do
    fields = {
      'id': 'ID!',
      'location': 'String!',
      'customId': 'String!'
    }

    expect(described_class).to match_schema_fields(fields)
  end
end

This approach has some drawbacks though:

  • The code in the matcher depends on the implementation of the class GraphQL::Schema::Object, any breaking changes will break the test suite after an update.
  • We're repeating code, the tests asserts the same fields in the type.
  • Writing these tests get tedious, and that makes devs less likely to write them.
Leveridge answered 6/7, 2018 at 4:12 Comment(0)
F
4

It looks you want to test your schema because you want to know if it is going to break the client. Basically you should avoid this.

Instead you can use gems like: graphql-schema_comparator to print breaking changes.

  1. I suggest to have a rake task for dumping your schema (and commit it in your repo).
  2. You can write some spec to check if the schema was dump - then you will make sure, you have always up-to date schema dump.
  3. Setup your CI to compare schema of current branch with schema on master branch.
  4. Fail your build if schema has dangerous or breaking changes.
  5. You can even generate Schema Changelog using schema-comparator ;) Or you can even use slack notifications to send any schema changes there so your team could easilly track any changes.
Falmouth answered 6/7, 2018 at 14:31 Comment(1)
graphql-schema_comparator looks very nice, thanks for pointing it out!Leveridge
L
1

What I feel is an improvement over the first approach I took is to use snapshot testing for the GraphQL Schema, instead of testing each of the types/mutation schemas one by one, I created a single test:

RSpec.describe MySchema do
  it 'renders the full schema' do
    schema = GraphQL::Schema::Printer.print_schema(MySchema)
    expect(schema).to match_snapshot('schema')
  end
end

This approach uses a slightly modified version of the rspec-snapshot gem, see my PR here.

The gem doesn't let you update the snapshot with a single command like in Jest, so I also created a rake task to delete the current snapshot:

namespace :tests do
  desc 'Deletes the schema snapshot'

  task delete_schema_snapshot: :environment do
    snapshot_path = Rails.root.join('spec', 'fixtures', 'snapshots', 'schema.snap')
    File.delete(snapshot_path) if File.exist?(snapshot_path)
  end
end

With this you'll get a pretty RSpec diff when the schema has been modified.

Leveridge answered 6/7, 2018 at 4:12 Comment(0)
R
1

The top-level Schema object has an #execute method. You can use this to write tests like

RSpec.describe MySchema do
  it 'fetches an object' do
    id = 'Zm9vOjE'
    query = <<~GRAPHQL
      query GetObject($id: ID!) {
        node(id: $id) { __typename id }
      }
    GRAPHQL
    res = described_class.execute(
      query,
      variables: { id: id }
    )
    expect(res['errors']).to be_nil
    expect(res['data']['node']['__typename']).to eq('Foo')
    expect(res['data']['node']['id']).to eq(id)
  end
end

The return value of the #execute method will be the conventional HTTP-style response, as a string-keyed hash. (Actually it's a GraphQL::Query::Result, but it delegates most things to an embedded hash.)

Ranite answered 6/7, 2018 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.