diff --git a/features/rspec/json_expectations/match_json_matcher.feature b/features/rspec/json_expectations/match_json_matcher.feature new file mode 100644 index 0000000..0a0e189 --- /dev/null +++ b/features/rspec/json_expectations/match_json_matcher.feature @@ -0,0 +1,74 @@ +Feature: match_json matcher + + As a developer extensively testing my APIs with RSpec + I want to have a suitable tool to test my API responses + And I want to use simple ruby hashes to describe the parts of response + For that I need a custom matcher + + Background: + Given a file "spec/spec_helper.rb" with: + """ruby + require "rspec/json_expectations" + """ + And a local "SIMPLE_JSON" with: + """json + { + "id": 25, + "email": "john.smith@example.com", + "name": "John" + } + """ + + Scenario: Expecting json string to include simple json + Given a file "spec/simple_example_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).to match_json( + id: 25, + email: "john.smith@example.com", + name: "John" + ) + end + end + """ + When I run "rspec spec/simple_example_spec.rb" + Then I see: + """ + 1 example, 0 failures + """ + + Scenario: Expecting wrong json string to include simple json + Given a file "spec/simple_with_fail_spec.rb" with: + """ruby + require "spec_helper" + + RSpec.describe "A json response" do + subject { '%{SIMPLE_JSON}' } + + it "has basic info about user" do + expect(subject).to match_json( + id: 25, + email: "john.smith@example.com", + ) + end + end + """ + When I run "rspec spec/simple_with_fail_spec.rb" + Then I see: + """ + 1 example, 1 failure + """ + And I see: + """ + expected: "{"id":25,"email":"john.smith@example.com","name":"John"}" + got: "{"id":25,"email":"john.smith@example.com"}" + """ + And I see: + """ruby + # ./spec/simple_with_fail_spec.rb + """ diff --git a/lib/rspec/json_expectations/json_traverser.rb b/lib/rspec/json_expectations/json_traverser.rb index 563c8fb..40aff8b 100644 --- a/lib/rspec/json_expectations/json_traverser.rb +++ b/lib/rspec/json_expectations/json_traverser.rb @@ -16,7 +16,7 @@ class JsonTraverser class << self def traverse(errors, expected, actual, negate=false, prefix=[], options={}) [ - handle_hash(errors, expected, actual, negate, prefix), + handle_hash(errors, expected, actual, negate, prefix, options), handle_array(errors, expected, actual, negate, prefix), handle_unordered(errors, expected, actual, negate, prefix, options), handle_value(errors, expected, actual, negate, prefix), @@ -40,10 +40,13 @@ def handle_keyvalue(errors, expected, actual, negate=false, prefix=[]) end.all? || false end - def handle_hash(errors, expected, actual, negate=false, prefix=[]) + def handle_hash(errors, expected, actual, negate=false, prefix=[], options={}) return nil unless expected.is_a?(Hash) - - handle_keyvalue(errors, expected, actual, negate, prefix) + if options[:json_match] + hash_size_matcher(errors, expected, actual, negate, prefix, options) + else + handle_keyvalue(errors, expected, actual, negate, prefix) + end end def handle_array(errors, expected, actual, negate=false, prefix=[]) @@ -80,6 +83,20 @@ def match_size_of_collection(errors, expected, actual, prefix, options) false end + def hash_size_matcher(errors, expected, actual, negate, prefix=[], options={}) + return nil unless options[:json_match] + if expected.size != actual.size + new_prefix = (expected.keys.map{|k| k.to_s} + actual.keys) - (actual.keys & expected.keys.map{|k| k.to_s}) + errors[new_prefix.join(",")] = { + expected: expected, + actual: actual, + } + false + else + handle_keyvalue(errors, expected, actual, negate, prefix) + end + end + def handle_value(errors, expected, actual, negate=false, prefix=[]) return nil unless handled_by_simple_value?(expected) @@ -131,7 +148,7 @@ def handle_rspec_matcher(errors, expected, actual, negate=false, prefix=[]) def handle_unsupported(expected) unless SUPPORTED_VALUES.any? { |type| expected.is_a?(type) } raise NotImplementedError, - "#{expected} expectation is not supported" + "#{expected} expectation is not supported" end end diff --git a/lib/rspec/json_expectations/matchers.rb b/lib/rspec/json_expectations/matchers.rb index 4adbd15..af68828 100644 --- a/lib/rspec/json_expectations/matchers.rb +++ b/lib/rspec/json_expectations/matchers.rb @@ -16,7 +16,30 @@ def traverse(expected, actual, negate=false) @include_json_errors = { _negate: negate }, expected, representation, - negate + negate, + ) + end +end + +RSpec::JsonExpectations::MatcherFactory.new(:match_json).define_matcher do + def traverse(expected, actual, negate=false) + unless expected.is_a?(Hash) || + expected.is_a?(Array) || + expected.is_a?(::RSpec::JsonExpectations::Matchers::UnorderedArrayMatcher) + raise ArgumentError, + "Expected value must be a json for match_json matcher" + end + + representation = actual + representation = JSON.parse(actual) if String === actual + + RSpec::JsonExpectations::JsonTraverser.traverse( + @include_json_errors = { _negate: negate }, + expected, + representation, + negate, + [], + { json_match: true } ) end end diff --git a/lib/rspec/json_expectations/version.rb b/lib/rspec/json_expectations/version.rb index b25296e..516e82b 100644 --- a/lib/rspec/json_expectations/version.rb +++ b/lib/rspec/json_expectations/version.rb @@ -1,5 +1,5 @@ module RSpec module JsonExpectations - VERSION = "2.2.0" + VERSION = "2.2.1" end end