DEV Community

R.J. Robinson
R.J. Robinson

Posted on

Testing GraphQL-Ruby Mutations With RSpec

xpost from medium - love Dev.to so much, wanted to share here as well.

To start with, a little OpEd on how much I love working with GraphQL. I started tinkering with it about a year ago, and have since completed a project at work where we did a rewrite, the results have been amazing. Pros and cons aside however, I needed a way to test.
This might not be the best solution for you, but this is what had worked on my team, and for our use case. 

The tests needed to cover a few aspects. Did the user have access? We use our own Pundit hack. Were the proper arguments sent? And did the response come back as expected? This sounds like a basic controller test, so from there I treated it as such.

Usually, in an RSpec controller test, we have a #get or a #post method that allows us to interact with that endpoint, and I wanted to continue with that pattern. These usually return a #response object with everything we would want to test.

module GraphQL::TestHelpers

  attr_accessor :gql_response

  # The returned results of a GraphQL query
  # @return [data] this is the bulk of the return to test.
  # @return [error] any time a query, mutation, subscription throws and error
  class GQLResponse
    attr_reader :data, :errors

    def initialize(args)
      @data = args['data'] || nil
      @errors = args['errors'] || nil
    end
  end

  # basic query to interact with the GraphQL API.
  # @param [query] required The query string that would be passed to the schema.
  def query(query, variables: {}, context: {})

    converted = variables.deep_transform_keys! {|key| key.to_s.camelize(:lower)} || {}

    res = MyRailsAppSchema.execute(query, variables: converted, context: context, operation_name: nil)
    @gql_response = GQLResponse.new(res.to_h)
  end

  alias_method :mutation, :query
end

Once I defined the #query and #mutation methods, passing in the arguments were easy.

mutation createBook($bookInput: BookInputType!){
  createBook(bookInput: $bookInput) {
    book {
      title
      author
      pages
    }
  }
}

#Arguments== 

{
  bookInputType: {
    title: "Testing GraphQL-Ruby Mutations With RSpec",
    author: "me", 
    pages: 1

  }
}

Say we have a simple book application, and we want to create a new book. 
Below are the tests, and while this seems very basic, I hope it's clear enough to understand the ideas behind it.

require 'rails_helper'
require_relative '../test_helpers'
include GraphQL::TestHelpers

describe 'CreateBook', type: :mutation do
  describe 'Creating a Book' do

    let(:user) {build_stubbed(:user)}
    let(:mutation_type) {"createBook"}
    let(:mutation_string) {<<- GQL
      mutation createBook($bookInput: BookInputType!){
       createBook(bookInput: $bookInput) {
          book {
            title
            author
            pages
          }
        }
      }
    GQL
    }

    context 'when a user has all the required permissions and parameters' do

      before do
        mutation mutation_string,
                 variables: {
                    bookInputType: {
                      title: "Testing GraphQL-Ruby Mutations With RSpec",
                      author: "me", 
                      pages: 1
                    }
                  }
                 },
                 context: {current_user: user}
      end

      it 'should return no errors' do
        expect(gql_response.errors).to be_nil
      end

      it 'should return the book object' do
        expect(gql_response.data[mutation_type]["book"]).to include("title" => "Testing GraphQL-Ruby Mutations With RSpec")
      end
    end

    context 'when a user has not passed required parameters parameters' do

      before do
        mutation mutation_string,
                 variables: {
                    bookInputType: {
                      title: "Testing GraphQL-Ruby Mutations With RSpec",
                    }
                  }
                 },
                 context: {current_user: user}
      end

      it 'should return errors' do
        expect(gql_response.errors).to be_truthy
      end

    end
  end
end

Why did I decide to do it this way? A few reasons. One, I wanted to treat the mutation like any other post request. Data goes in, data comes out. I see a lot of tests trying to do some crazy things just to hit the resolver inside their mutation. I don't really think that's needed. Using the existing interface with the GraphQL Schema should test what you need to. Hope this helps, and I how you provide some feedback. I would love to see how everyone else tests.

I can be found on twitter. :) @rjrobinson

Top comments (1)

Collapse
 
arthurkulchenko profile image
Artur • Edited

I don't really think that's needed.

And you are right, on my project it allowed me to reduce execution time almost in a half.