Skip to content

Commit

Permalink
Improve error messages for have_a_field and fix type checking bug
Browse files Browse the repository at this point in the history
  • Loading branch information
khamusa committed Feb 10, 2017
1 parent 3248c6c commit 8189db8
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 20 deletions.
11 changes: 11 additions & 0 deletions lib/rspec/graphql_matchers/base_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module RSpec
module GraphqlMatchers
class BaseMatcher
private

def types_match?(actual_type, expected_type)
expected_type.nil? || expected_type.to_s == actual_type.to_s
end
end
end
end
52 changes: 37 additions & 15 deletions lib/rspec/graphql_matchers/have_a_field.rb
Original file line number Diff line number Diff line change
@@ -1,44 +1,66 @@
require_relative 'base_matcher'

module RSpec
module GraphqlMatchers
class HaveAField
def initialize(field_name)
@field_name = field_name.to_s
@field_type = @graph_object = nil
class HaveAField < BaseMatcher
def initialize(expected_field_name)
@expected_field_name = expected_field_name.to_s
@expected_field_type = @graph_object = nil
end

def matches?(graph_object)
@graph_object = graph_object

actual_field = @graph_object.fields[@field_name]
actual_field && types_match?(@field_type, actual_field.type)
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]
valid_field? && types_match?(@actual_field.type, @expected_field_type)
end

def that_returns(field_type)
@field_type = field_type
def that_returns(expected_field_type)
@expected_field_type = expected_field_type

self
end
alias returning that_returns
alias of_type that_returns

def failure_message
"expected #{describe_obj(@graph_object)} to #{description}"
"expected #{describe_obj(@graph_object)} to " \
"#{description}, #{explanation}."
end

def description
"define field `#{@field_name}`" + of_type_description
"define field `#{@expected_field_name}`" + of_type_description
end

private

def of_type_description
return '' unless @field_type
def explanation
return 'but no field was found with that name' unless @actual_field

"but the field type was `#{@actual_field.type}`"
end

def valid_field?
unless @expected_field_type.nil? || @actual_field.respond_to?(:type)
error_msg = "The `#{@expected_field_name}` field defined by the GraphQL " \
'object does\'t seem valid as it does not respond to #type. ' \
"\n\n\tThe field found was #{@actual_field.inspect}. "
puts error_msg
raise error_msg
end

" of type `#{@field_type}`"
@actual_field
end

def types_match?(expected_type, actual_type)
!expected_type || expected_type.to_s == actual_type.to_s
def of_type_description
return '' unless @expected_field_type

" of type `#{@expected_field_type}`"
end

def describe_obj(field)
Expand Down
57 changes: 52 additions & 5 deletions spec/rspec/have_a_field_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@

module RSpec::GraphqlMatchers
describe 'expect(a_type).to have_a_field(field_name).that_returns(a_type)' do
subject(:a_type) { double(:type, fields: type_fields) }
let(:type_fields) { { 'id' => double(:field, type: types.String) } }
subject(:a_type) do
types_to_define = type_fields
GraphQL::ObjectType.define do
name 'TestObject'

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

it { is_expected.to have_a_field(:id) }

Expand All @@ -17,7 +26,7 @@ module RSpec::GraphqlMatchers

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

it 'provides a description' do
Expand All @@ -27,15 +36,53 @@ module RSpec::GraphqlMatchers
expect(matcher.description).to eq('define field `id`')
end

it 'passes when the type defines the field with correct type' do
it 'passes when the type defines the field with correct type as strings' do
expect(a_type).to have_a_field(:id).that_returns('String')
expect(a_type).to have_a_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_field(:id).that_returns(types.String)
expect(a_type).to have_a_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_field(:id).returning('String!') }
.to fail_with(
"expected #{a_type.inspect} to define field `id` of type `String!`"
"expected #{a_type.inspect} to define field `id` of type `String!`," \
' but the field type was `String`.'
)

expect { expect(a_type).to have_a_field('other').returning(!types.Int) }
.to fail_with(
"expected #{a_type.inspect} 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_field(:id) }
.to raise_error(
RuntimeError,
'Invalid object InvalidObject provided to have_a_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.fields)
.to receive(:[]).and_return double(inspect: 'AnInvalidField')
end

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

0 comments on commit 8189db8

Please sign in to comment.