From ca70d2f4a0a8ed48632ea18accc32fa34a0782ff Mon Sep 17 00:00:00 2001 From: Brian Glusman Date: Wed, 11 Jun 2014 10:15:36 -0400 Subject: [PATCH 1/5] first pass nested matcher --- README.md | 3 +- lib/json_spec/matchers.rb | 5 + lib/json_spec/matchers/include_nested_json.rb | 40 ++++++++ .../matchers/include_nested_json_spec.rb | 94 +++++++++++++++++++ 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 lib/json_spec/matchers/include_nested_json.rb create mode 100644 spec/json_spec/matchers/include_nested_json_spec.rb diff --git a/README.md b/README.md index 70f4da3..8eec783 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` 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..98bac12 --- /dev/null +++ b/lib/json_spec/matchers/include_nested_json.rb @@ -0,0 +1,40 @@ +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) + expected = exclude_keys(parse_json(@expected_json)) + case actual + when Hash then actual.values.map{|v| exclude_keys(v) }.include?(expected) || matches_nested(actual) + when Array then actual.map{|e| exclude_keys(e) }.include?(expected) || matches_nested(actual) + when String then actual.include?(expected) + else false + end + end + + def matches_nested(parsed_json) + case parsed_json + when Hash then parsed_json.values.reduce(false) {|accum, value| accum || test_if_nests(value)} + when Array then parsed_json.reduce(false) {|accum, value| accum || test_if_nests(value)} + else false + end + end + + def test_if_nests(value) + value.respond_to?(:each) && matches?(value.to_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..199ad04 --- /dev/null +++ b/spec/json_spec/matchers/include_nested_json_spec.rb @@ -0,0 +1,94 @@ +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 + + + it "matches included array elements" do + json = %(["one",1,1.0,true,false,null]) + json.should include_nested_json(%("one")) + json.should include_nested_json(%(1)) + json.should include_nested_json(%(1.0)) + json.should include_nested_json(%(true)) + json.should include_nested_json(%(false)) + json.should include_nested_json(%(null)) + end + + it "matches an array included in an array" do + json = %([[1,2,3],[4,5,6]]) + json.should include_nested_json(%([1,2,3])) + json.should include_nested_json(%([4,5,6])) + end + + it "matches a hash included in an array" do + json = %([{"one":1},{"two":2}]) + json.should include_nested_json(%({"one":1})) + json.should include_nested_json(%({"two":2})) + end + + it "matches included hash values" do + json = %({"string":"one","integer":1,"float":1.0,"true":true,"false":false,"null":null}) + json.should include_nested_json(%("one")) + json.should include_nested_json(%(1)) + json.should include_nested_json(%(1.0)) + json.should include_nested_json(%(true)) + json.should include_nested_json(%(false)) + json.should include_nested_json(%(null)) + end + + it "matches a hash included in a hash" do + json = %({"one":{"two":3},"four":{"five":6}}) + json.should include_nested_json(%({"two":3})) + json.should include_nested_json(%({"five":6})) + end + + it "matches an array included in a hash" do + json = %({"one":[2,3],"four":[5,6]}) + json.should include_nested_json(%([2,3])) + json.should include_nested_json(%([5,6])) + end + + it "matches a substring" do + json = %("json") + json.should include_nested_json(%("js")) + json.should include_nested_json(%("json")) + end + + it "ignores excluded keys" do + %([{"id":1,"two":3}]).should include_nested_json(%({"two":3})) + end + + it "provides a description message" do + matcher = include_nested_json(%({"json":"spec"})) + matcher.matches?(%({"id":1,"json":"spec"})) + matcher.description.should == "include JSON" + end + + it "raises an error when not given expected JSON" do + expect{ %([{"id":1,"two":3}]).should include_nested_json }.to raise_error + end + + it "matches file contents" do + JsonSpec.directory = files_path + %({"one":{"value":"from_file"},"four":{"five":6}}).should include_nested_json.from_file("one.json") + end +end From 5ad8a576ce4aec3d832f07fb159cae44099a1215 Mon Sep 17 00:00:00 2001 From: Brian Glusman Date: Wed, 11 Jun 2014 12:28:22 -0400 Subject: [PATCH 2/5] refactor includes_nested_json and add README example --- README.md | 6 ++++++ lib/json_spec/matchers/include_nested_json.rb | 21 ++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8eec783..bc83a79 100644 --- a/README.md +++ b/README.md @@ -41,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/include_nested_json.rb b/lib/json_spec/matchers/include_nested_json.rb index 98bac12..a5eb086 100644 --- a/lib/json_spec/matchers/include_nested_json.rb +++ b/lib/json_spec/matchers/include_nested_json.rb @@ -5,16 +5,19 @@ class IncludeNestedJson < IncludeJson def matches?(actual_json) raise "Expected included JSON not provided" if @expected_json.nil? actual = parse_json(actual_json) - expected = exclude_keys(parse_json(@expected_json)) - case actual - when Hash then actual.values.map{|v| exclude_keys(v) }.include?(expected) || matches_nested(actual) - when Array then actual.map{|e| exclude_keys(e) }.include?(expected) || matches_nested(actual) - when String then actual.include?(expected) + matches_nested?(actual) + end + + def matches_nested?(parsed_json) + case parsed_json + when Hash then parsed_json.values.map{|v| exclude_keys(v) }.include?(expected) || test_contained_enumerables(parsed_json) + when Array then 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 matches_nested(parsed_json) + def test_contained_enumerables(parsed_json) case parsed_json when Hash then parsed_json.values.reduce(false) {|accum, value| accum || test_if_nests(value)} when Array then parsed_json.reduce(false) {|accum, value| accum || test_if_nests(value)} @@ -23,7 +26,11 @@ def matches_nested(parsed_json) end def test_if_nests(value) - value.respond_to?(:each) && matches?(value.to_json) + value.respond_to?(:each) && matches_nested?(value) + end + + def expected + @expected ||= exclude_keys(parse_json(@expected_json)) end def failure_message_for_should From a04618e8abb108f523280f2bb815dfeba83724cd Mon Sep 17 00:00:00 2001 From: Brian Glusman Date: Wed, 11 Jun 2014 13:04:25 -0400 Subject: [PATCH 3/5] removing duplicated tests from include_json spec --- .../matchers/include_nested_json_spec.rb | 70 ------------------- 1 file changed, 70 deletions(-) diff --git a/spec/json_spec/matchers/include_nested_json_spec.rb b/spec/json_spec/matchers/include_nested_json_spec.rb index 199ad04..dd5f7f1 100644 --- a/spec/json_spec/matchers/include_nested_json_spec.rb +++ b/spec/json_spec/matchers/include_nested_json_spec.rb @@ -21,74 +21,4 @@ end end end - - - it "matches included array elements" do - json = %(["one",1,1.0,true,false,null]) - json.should include_nested_json(%("one")) - json.should include_nested_json(%(1)) - json.should include_nested_json(%(1.0)) - json.should include_nested_json(%(true)) - json.should include_nested_json(%(false)) - json.should include_nested_json(%(null)) - end - - it "matches an array included in an array" do - json = %([[1,2,3],[4,5,6]]) - json.should include_nested_json(%([1,2,3])) - json.should include_nested_json(%([4,5,6])) - end - - it "matches a hash included in an array" do - json = %([{"one":1},{"two":2}]) - json.should include_nested_json(%({"one":1})) - json.should include_nested_json(%({"two":2})) - end - - it "matches included hash values" do - json = %({"string":"one","integer":1,"float":1.0,"true":true,"false":false,"null":null}) - json.should include_nested_json(%("one")) - json.should include_nested_json(%(1)) - json.should include_nested_json(%(1.0)) - json.should include_nested_json(%(true)) - json.should include_nested_json(%(false)) - json.should include_nested_json(%(null)) - end - - it "matches a hash included in a hash" do - json = %({"one":{"two":3},"four":{"five":6}}) - json.should include_nested_json(%({"two":3})) - json.should include_nested_json(%({"five":6})) - end - - it "matches an array included in a hash" do - json = %({"one":[2,3],"four":[5,6]}) - json.should include_nested_json(%([2,3])) - json.should include_nested_json(%([5,6])) - end - - it "matches a substring" do - json = %("json") - json.should include_nested_json(%("js")) - json.should include_nested_json(%("json")) - end - - it "ignores excluded keys" do - %([{"id":1,"two":3}]).should include_nested_json(%({"two":3})) - end - - it "provides a description message" do - matcher = include_nested_json(%({"json":"spec"})) - matcher.matches?(%({"id":1,"json":"spec"})) - matcher.description.should == "include JSON" - end - - it "raises an error when not given expected JSON" do - expect{ %([{"id":1,"two":3}]).should include_nested_json }.to raise_error - end - - it "matches file contents" do - JsonSpec.directory = files_path - %({"one":{"value":"from_file"},"four":{"five":6}}).should include_nested_json.from_file("one.json") - end end From 7397b1dcdacd5a78176cdacc2ace9cb7bea5e463 Mon Sep 17 00:00:00 2001 From: Brian Glusman Date: Wed, 11 Jun 2014 13:13:13 -0400 Subject: [PATCH 4/5] clean long lines and refactor for readability --- lib/json_spec/matchers/include_nested_json.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/json_spec/matchers/include_nested_json.rb b/lib/json_spec/matchers/include_nested_json.rb index a5eb086..02dc745 100644 --- a/lib/json_spec/matchers/include_nested_json.rb +++ b/lib/json_spec/matchers/include_nested_json.rb @@ -10,8 +10,12 @@ def matches?(actual_json) def matches_nested?(parsed_json) case parsed_json - when Hash then parsed_json.values.map{|v| exclude_keys(v) }.include?(expected) || test_contained_enumerables(parsed_json) - when Array then parsed_json.map{|e| exclude_keys(e) }.include?(expected) || test_contained_enumerables(parsed_json) + when Hash + parsed_json.values.map{|v| exclude_keys(v) }.include?(expected) || + test_contained_enumerables(parsed_json) + 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 @@ -19,14 +23,16 @@ def matches_nested?(parsed_json) def test_contained_enumerables(parsed_json) case parsed_json - when Hash then parsed_json.values.reduce(false) {|accum, value| accum || test_if_nests(value)} - when Array then parsed_json.reduce(false) {|accum, value| accum || test_if_nests(value)} + when Hash then test_each(parsed_json.values) + when Array then test_each(parsed_json) else false end end - def test_if_nests(value) - value.respond_to?(:each) && matches_nested?(value) + def test_each(values) + values.map do |value| + value.respond_to?(:each) && matches_nested?(value) + end.detect {|test| !!test } end def expected From 9a4bed51ba66879d8e184d20d6b7c9b5504b609d Mon Sep 17 00:00:00 2001 From: Brian Glusman Date: Wed, 11 Jun 2014 13:47:10 -0400 Subject: [PATCH 5/5] eliminate test_each and second case statement --- lib/json_spec/matchers/include_nested_json.rb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/json_spec/matchers/include_nested_json.rb b/lib/json_spec/matchers/include_nested_json.rb index 02dc745..b3c527e 100644 --- a/lib/json_spec/matchers/include_nested_json.rb +++ b/lib/json_spec/matchers/include_nested_json.rb @@ -12,7 +12,7 @@ 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) + test_contained_enumerables(parsed_json.values) when Array parsed_json.map{|e| exclude_keys(e) }.include?(expected) || test_contained_enumerables(parsed_json) @@ -21,15 +21,7 @@ def matches_nested?(parsed_json) end end - def test_contained_enumerables(parsed_json) - case parsed_json - when Hash then test_each(parsed_json.values) - when Array then test_each(parsed_json) - else false - end - end - - def test_each(values) + def test_contained_enumerables(values) values.map do |value| value.respond_to?(:each) && matches_nested?(value) end.detect {|test| !!test }