Skip to content

Commit

Permalink
Merge pull request khamusa#4 from aaronklaassen/mutation-matchers
Browse files Browse the repository at this point in the history
Mutation matchers
  • Loading branch information
khamusa authored May 10, 2017
2 parents 8189db8 + bfea3e2 commit 2b437eb
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 7 deletions.
27 changes: 20 additions & 7 deletions lib/rspec/graphql_matchers/have_a_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@
module RSpec
module GraphqlMatchers
class HaveAField < BaseMatcher
def initialize(expected_field_name)
def initialize(expected_field_name, fields = :fields)
@expected_field_name = expected_field_name.to_s
@expected_field_type = @graph_object = nil
@fields = fields.to_sym
end

def matches?(graph_object)
@graph_object = graph_object

unless @graph_object.respond_to?(:fields)
raise "Invalid object #{@graph_object} provided to have_a_field " \
'matcher. It does not seem to be a valid GraphQL object type.'
end

@actual_field = @graph_object.fields[@expected_field_name]
@actual_field = field_collection[@expected_field_name]
valid_field? && types_match?(@actual_field.type, @expected_field_type)
end

Expand Down Expand Up @@ -66,6 +62,23 @@ def of_type_description
def describe_obj(field)
field.respond_to?(:name) && field.name || field.inspect
end

def field_collection
if @graph_object.respond_to?(@fields)
@graph_object.public_send(@fields)
else
raise "Invalid object #{@graph_object} provided to #{matcher_name} " \
'matcher. It does not seem to be a valid GraphQL object type.'
end
end

def matcher_name
case @fields
when :fields then 'have_a_field'
when :input_fields then 'have_an_input_field'
when :return_fields then 'have_a_return_field'
end
end
end
end
end
12 changes: 12 additions & 0 deletions lib/rspec/graphql_matchers/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,17 @@ def have_a_field(field_name)
RSpec::GraphqlMatchers::HaveAField.new(field_name)
end
alias have_field have_a_field

# rubocop:disable Style/PredicateName
def have_an_input_field(field_name)
RSpec::GraphqlMatchers::HaveAField.new(field_name, :input_fields)
end
alias have_input_field have_an_input_field

# rubocop:disable Style/PredicateName
def have_a_return_field(field_name)
RSpec::GraphqlMatchers::HaveAField.new(field_name, :return_fields)
end
alias have_return_field have_a_return_field
end
end
88 changes: 88 additions & 0 deletions spec/rspec/have_a_return_field_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require 'spec_helper'

module RSpec::GraphqlMatchers
describe 'expect(a_type).to have_a_return_field(field_name).that_returns(a_type)' do
subject(:a_type) do
types_to_define = type_fields
GraphQL::Relay::Mutation.define do
name 'TestObject'

types_to_define.each do |fname, ftype|
return_field fname, ftype
end
end
end
let(:type_fields) { { 'id' => types.String, 'other' => !types.ID } }

it { is_expected.to have_a_return_field(:id) }

it 'passes when the type defines the field' do
expect(a_type).to have_a_return_field(:id)
end

it 'fails when the type does not define the expected field' do
expect(a_type).not_to have_a_return_field(:ids)
end

it 'fails with a failure message when the type does not define the field' do
expect { expect(a_type).to have_a_return_field(:ids) }
.to fail_with("expected #{a_type.name} to define field `ids`, but no field was found with that name.")
end

it 'provides a description' do
matcher = have_a_return_field(:id)
matcher.matches?(a_type)

expect(matcher.description).to eq('define field `id`')
end

it 'passes when the type defines the field with correct type as strings' do
expect(a_type).to have_a_return_field(:id).that_returns('String')
expect(a_type).to have_a_return_field('other').that_returns('ID!')
end

it 'passes when the type defines the field with correct type as graphql objects' do
expect(a_type).to have_a_return_field(:id).that_returns(types.String)
expect(a_type).to have_a_return_field('other').that_returns(!types.ID)
end

it 'fails when the type defines a field of the wrong type' do
expect { expect(a_type).to have_a_return_field(:id).returning('String!') }
.to fail_with(
"expected #{a_type.name} to define field `id` of type `String!`," \
' but the field type was `String`.'
)

expect { expect(a_type).to have_a_return_field('other').returning(!types.Int) }
.to fail_with(
"expected #{a_type.name} to define field `other` of type `Int!`," \
' but the field type was `ID!`.'
)
end

context 'when an invalid type is passed' do
let(:a_type) { double(to_s: 'InvalidObject') }

it 'fails with a Runtime error' do
expect { expect(a_type).to have_a_return_field(:id) }
.to raise_error(
RuntimeError,
'Invalid object InvalidObject provided to have_a_return_field matcher. ' \
'It does not seem to be a valid GraphQL object type.'
)
end
end

context 'when a field is found but it does not seem a valid graphql field' do
before do
allow(a_type.return_fields)
.to receive(:[]).and_return double(inspect: 'AnInvalidField')
end

it 'fails with a Runtime error' do
expect { expect(a_type).to have_a_return_field(:id).of_type(!types.Int) }
.to raise_error(RuntimeError)
end
end
end
end
88 changes: 88 additions & 0 deletions spec/rspec/have_an_input_field_matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require 'spec_helper'

module RSpec::GraphqlMatchers
describe 'expect(a_type).to have_an_input_field(field_name).that_returns(a_type)' do
subject(:a_type) do
types_to_define = type_fields
GraphQL::Relay::Mutation.define do
name 'TestObject'

types_to_define.each do |fname, ftype|
input_field fname, ftype
end
end
end
let(:type_fields) { { 'id' => types.String, 'other' => !types.ID } }

it { is_expected.to have_an_input_field(:id) }

it 'passes when the type defines the field' do
expect(a_type).to have_an_input_field(:id)
end

it 'fails when the type does not define the expected field' do
expect(a_type).not_to have_an_input_field(:ids)
end

it 'fails with a failure message when the type does not define the field' do
expect { expect(a_type).to have_an_input_field(:ids) }
.to fail_with("expected #{a_type.name} to define field `ids`, but no field was found with that name.")
end

it 'provides a description' do
matcher = have_an_input_field(:id)
matcher.matches?(a_type)

expect(matcher.description).to eq('define field `id`')
end

it 'passes when the type defines the field with correct type as strings' do
expect(a_type).to have_an_input_field(:id).that_returns('String')
expect(a_type).to have_an_input_field('other').that_returns('ID!')
end

it 'passes when the type defines the field with correct type as graphql objects' do
expect(a_type).to have_an_input_field(:id).that_returns(types.String)
expect(a_type).to have_an_input_field('other').that_returns(!types.ID)
end

it 'fails when the type defines a field of the wrong type' do
expect { expect(a_type).to have_an_input_field(:id).returning('String!') }
.to fail_with(
"expected #{a_type.name} to define field `id` of type `String!`," \
' but the field type was `String`.'
)

expect { expect(a_type).to have_an_input_field('other').returning(!types.Int) }
.to fail_with(
"expected #{a_type.name} to define field `other` of type `Int!`," \
' but the field type was `ID!`.'
)
end

context 'when an invalid type is passed' do
let(:a_type) { double(to_s: 'InvalidObject') }

it 'fails with a Runtime error' do
expect { expect(a_type).to have_an_input_field(:id) }
.to raise_error(
RuntimeError,
'Invalid object InvalidObject provided to have_an_input_field matcher. ' \
'It does not seem to be a valid GraphQL object type.'
)
end
end

context 'when a field is found but it does not seem a valid graphql field' do
before do
allow(a_type.input_fields)
.to receive(:[]).and_return double(inspect: 'AnInvalidField')
end

it 'fails with a Runtime error' do
expect { expect(a_type).to have_an_input_field(:id).of_type(!types.Int) }
.to raise_error(RuntimeError)
end
end
end
end

0 comments on commit 2b437eb

Please sign in to comment.