diff --git a/README.md b/README.md index 70f4da3..bc83a79 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@ Easily handle JSON in RSpec and Cucumber RSpec -------------- -json_spec defines five new RSpec matchers: +json_spec defines six new RSpec matchers: * `be_json_eql` * `include_json` +# `include_nested_json` * `have_json_path` * `have_json_type` * `have_json_size` @@ -40,6 +41,12 @@ describe User do user.to_json.should have_json_size(1).at_path("friends") user.to_json.should include_json(friend.to_json) end + + it "includes user id within response" do + new_user = User.create!(first_name: "Catie", last_name: "Richert") + users_json = User.page(1).to_json + users_json.should include_nested_json("{id: #{new_user.id}}") + end end end ``` diff --git a/lib/json_spec/matchers.rb b/lib/json_spec/matchers.rb index cf58e89..6d9a8ba 100644 --- a/lib/json_spec/matchers.rb +++ b/lib/json_spec/matchers.rb @@ -1,5 +1,6 @@ require "json_spec/matchers/be_json_eql" require "json_spec/matchers/include_json" +require "json_spec/matchers/include_nested_json" require "json_spec/matchers/have_json_path" require "json_spec/matchers/have_json_type" require "json_spec/matchers/have_json_size" @@ -14,6 +15,10 @@ def include_json(json = nil) JsonSpec::Matchers::IncludeJson.new(json) end + def include_nested_json(json = nil) + JsonSpec::Matchers::IncludeNestedJson.new(json) + end + def have_json_path(path) JsonSpec::Matchers::HaveJsonPath.new(path) end diff --git a/lib/json_spec/matchers/include_nested_json.rb b/lib/json_spec/matchers/include_nested_json.rb new file mode 100644 index 0000000..b3c527e --- /dev/null +++ b/lib/json_spec/matchers/include_nested_json.rb @@ -0,0 +1,45 @@ +module JsonSpec + module Matchers + class IncludeNestedJson < IncludeJson + + def matches?(actual_json) + raise "Expected included JSON not provided" if @expected_json.nil? + actual = parse_json(actual_json) + matches_nested?(actual) + end + + def matches_nested?(parsed_json) + case parsed_json + when Hash + parsed_json.values.map{|v| exclude_keys(v) }.include?(expected) || + test_contained_enumerables(parsed_json.values) + when Array + parsed_json.map{|e| exclude_keys(e) }.include?(expected) || + test_contained_enumerables(parsed_json) + when String then parsed_json.include?(expected) + else false + end + end + + def test_contained_enumerables(values) + values.map do |value| + value.respond_to?(:each) && matches_nested?(value) + end.detect {|test| !!test } + end + + def expected + @expected ||= exclude_keys(parse_json(@expected_json)) + end + + def failure_message_for_should + message_with_path("Expected 'actual' to include nested 'expected' JSON") + end + + def failure_message_for_should_not + message_with_path("Expected 'actual' to not include nested 'expected' JSON") + end + + undef at_path + end + end +end diff --git a/spec/json_spec/matchers/include_nested_json_spec.rb b/spec/json_spec/matchers/include_nested_json_spec.rb new file mode 100644 index 0000000..dd5f7f1 --- /dev/null +++ b/spec/json_spec/matchers/include_nested_json_spec.rb @@ -0,0 +1,24 @@ +require "spec_helper" + +describe JsonSpec::Matchers::IncludeNestedJson do + + describe "matching nested values" do + json_strings = [ + %({"fname":"jolly", "lname":"roger", "partners": [{"fname": "dread pirate", "lname": "roberts"}]}), + %({"fname":"jolly", "lname":"roger", "partner": {"fname": "dread pirate", "lname": "roberts"}}), + %({"fname":"jolly", "lname":"roger", "connections": {"partners": [{"fname": "dread pirate", "lname": "roberts"}]}}) + ] + + it "matches values regardless of path depth" do + json_strings.each do |json_string| + json_string.should include_nested_json '{"fname": "dread pirate", "lname": "roberts"}' + end + end + + it "does not match value split across depths" do + json_strings.each do |json_string| + json_string.should_not include_nested_json '{"fname": "dread pirate", "lname": "roger"}' + end + end + end +end