Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions features/rspec/json_expectations/match_json_matcher.feature
Original file line number Diff line number Diff line change
@@ -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": "[email protected]",
"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: "[email protected]",
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: "[email protected]",
)
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":"[email protected]","name":"John"}"
got: "{"id":25,"email":"[email protected]"}"
"""
And I see:
"""ruby
# ./spec/simple_with_fail_spec.rb
"""
27 changes: 22 additions & 5 deletions lib/rspec/json_expectations/json_traverser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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=[])
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down
25 changes: 24 additions & 1 deletion lib/rspec/json_expectations/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/json_expectations/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module RSpec
module JsonExpectations
VERSION = "2.2.0"
VERSION = "2.2.1"
end
end