rspec_json_matchers 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f8195ff8f9a4378643145f3199e37b00edca07a3
4
+ data.tar.gz: 60c5a6ba469eeb3b29084580d4b283031e9948fb
5
+ SHA512:
6
+ metadata.gz: f7750332e4a7083e58fa3afe934f322947a43c2167d5cfbbc48e18b5ae5f7cfddf309be0c0a598d441684a21b665f11b7fb21a077fec444f30d1e9363f1d79cf
7
+ data.tar.gz: 52489d95dbd36bee0bf2e44a82266e4d62c01960552cbd45aa654541a1feac2a486d53a19e86a4845aa0cd58b1e39087d9dc8d1df54331b27fe6ae517c9ecee1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rspec', '~> 3.0'
4
+ gem 'activesupport', '~> 4.0.2'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Brigade
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # rspec_json_matchers
2
+
3
+ Are you tired of unreadable output while verifying your API's JSON output? What
4
+ about duplicating and losing track of your JSON structure definitions?
5
+ `rspec_json_matchers` provides declarative JSON structure definitions and
6
+ matchers with clear error output.
7
+
8
+ ## How to use
9
+
10
+ ### Add it to your `Gemfile`
11
+ ```ruby
12
+ gem 'rspec_json_matchers'
13
+ ```
14
+
15
+ ### Add config `spec/spec_helper.rb`
16
+ ```ruby
17
+ RSpec.configure do |config|
18
+ config.include RSpecJsonMatchers
19
+ end
20
+ ```
21
+
22
+ Optionally if you choose to define your matchers in a their own directory,
23
+ import them explicitly like this:
24
+
25
+ ```ruby
26
+ Dir[Rails.root.join('spec/json_matchers/**/*.rb')].each { |f| require f }
27
+ ```
28
+
29
+ ### Define your matchers
30
+ ```ruby
31
+ RSpecJsonMatchers.define_api_matcher :json_object do
32
+ strings { an_instance_of(String) }
33
+ integers { a_kind_of(Integer) }
34
+ actual_values { 10 }
35
+ keys_that_should_not_exist { absent }
36
+ booleans { a_boolean_value }
37
+ nilable { a_nil_value.or(a_kind_of(Integer)) }
38
+ another_object { a_serialized_other_json_object }
39
+
40
+ nested_structure do
41
+ match_api_response(
42
+ foo: an_instance_of(String),
43
+ bar: 'baz'
44
+ )
45
+ end
46
+ end
47
+ ```
48
+
49
+ Your matchers will be available as `a_serialized_object_name` and can be used
50
+ through `match_api_response(a_serialized_object_name)` or
51
+ `be_a_serialized_object_name`
52
+
53
+
54
+ ### Write your test!
55
+
56
+ See the `spec/` folder for an example
57
+
58
+
59
+ ## Authors
60
+ * Ryan Fitzgerald [rf-]
61
+ * Hao Su [haosu]
@@ -0,0 +1,30 @@
1
+ module RSpecJsonMatchers
2
+ # This is a special matcher that always returns false under normal
3
+ # circumstances, used to indicate keys that should not be present in a given
4
+ # API response.
5
+ #
6
+ # The only circumstance where the matcher may return true is in the class
7
+ # method `is_an_absence_matcher?`, which takes a matcher and feeds a special
8
+ # `ABSENCE_MARKER` value into it which should fail every possible matcher
9
+ # other than AbsenceMatcher, which it passes. We can use this to determine
10
+ # whether it's OK for a given key to be absent from a response. TODO: try to
11
+ # make this explanation less confusing.
12
+ class AbsenceMatcher < RSpec::Matchers::BuiltIn::Equal
13
+ ABSENCE_MARKER = Object.new
14
+
15
+ # Test whether the given matcher is either an instance of AbsenceMatcher or
16
+ # a compound matcher containing an AbsenceMatcher, like
17
+ # 'absent.or(a_kind_of(Integer))'.
18
+ def self.is_an_absence_matcher?(matcher)
19
+ matcher === ABSENCE_MARKER && !(matcher === Object.new)
20
+ end
21
+
22
+ def initialize
23
+ @expected = ABSENCE_MARKER
24
+ end
25
+
26
+ def description
27
+ 'absent'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,76 @@
1
+ module RSpecJsonMatchers
2
+ # General matcher for API responses, used to compose matchers for the overall
3
+ # API response or individual model serializations.
4
+ class ApiResponseMatcher < RSpec::Matchers::BuiltIn::BaseMatcher
5
+ attr_reader :results_with_errors
6
+
7
+ def initialize(expected,
8
+ object_definition: nil,
9
+ object_name: nil,
10
+ context: nil)
11
+ @expected = expected
12
+ @object_definition = object_definition
13
+ @object_name = object_name
14
+ @context = context
15
+ end
16
+
17
+ # Use our implementation of FuzzyMatcher to test the given data against our
18
+ # expected data.
19
+ # @param expected [Object] Expected values or matchers
20
+ # @param actual [Object] Actual data to be validated
21
+ # @return [Boolean]
22
+ def matches?(actual)
23
+ @actual = actual
24
+
25
+ if @object_definition
26
+ combined = @expected.reverse_merge @object_definition.to_hash(@context)
27
+ end
28
+
29
+ @did_match, @results_with_errors =
30
+ FuzzyMatcher.match_values(combined || @expected, @actual)
31
+ @did_match
32
+ end
33
+
34
+ # Overrides `failure_message` to return our own field specific failure
35
+ # messsages with color!
36
+ # @return [String]
37
+ def failure_message
38
+ "Expected API response to match specification:\n#{pretty_results}"
39
+ end
40
+
41
+ # Overrides `failure_message_when_negated` when testing that an API response
42
+ # does not include a resource.
43
+ # @return [String]
44
+ def failure_message_when_negated
45
+ "Expected API response not to match specification, but it did:\n#{pretty_results}"
46
+ end
47
+
48
+ # Formats our results object with PP and, resetting the color at the
49
+ # beginning of each line to override RSpec's default coloring.
50
+ # @return [String]
51
+ def pretty_results
52
+ @results_with_errors.pretty_inspect.gsub(/^/, "\e[0m")
53
+ end
54
+
55
+ # If we have explicitly defined the type of API matcher we want, report
56
+ # that, otherwise describe the general API matcher
57
+ # @return [String]
58
+ def description
59
+ description = surface_descriptions_in(@expected).inspect
60
+
61
+ if @object_name
62
+ "a serialized #{@object_name}" \
63
+ "#{" matching #{description}" if description != '{}'}"
64
+ else
65
+ "an api response matching #{description}"
66
+ end
67
+ end
68
+
69
+ # Overridden because we don't want to inspect `@object_definition` and
70
+ # `@context`
71
+ # @return [String]
72
+ def inspect
73
+ "#<ApiResponseMatcher#{" for #{@object_name}" if @object_name}>"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,210 @@
1
+ module RSpecJsonMatchers
2
+ # This is a fork of `RSpec::Support::FuzzyMatcher`, which is a tool used to
3
+ # recursively match a given structure (of arrays, hashes, and other objects)
4
+ # against an expected structure. The expected structure can contain scalar
5
+ # values or RSpec matchers; leaves are compared using the `===` operator.
6
+ #
7
+ # The differences between this module and the RSpec version are:
8
+ # * Instead of implementing predicate methods that return true or false (and
9
+ # bail out of the recursion as soon as they notice a problem), our methods
10
+ # return both a success value and a representation of the structure that
11
+ # includes failure messages. That lets us show exactly where the structures
12
+ # differed in a way that's more reliable than textual diffing.
13
+ # * We support `absent` (`RSpecJsonMatchers::AbsenceMatcher`) as a placeholder
14
+ # that means "this key should not exist in the hash we're matching
15
+ # against".
16
+ module FuzzyMatcher
17
+ extend RSpec::Matchers::Composable # for surface_descriptions_in
18
+
19
+ # Simple wrapper for formatting individual errors in a failure message.
20
+ class FailureDescription
21
+ # @param message [String] Message to be rendered.
22
+ def initialize(message)
23
+ @message = message
24
+ end
25
+
26
+ # @return [String] the given message, with extra formatting and red
27
+ # highlighting.
28
+ def inspect
29
+ "\e[31m(FAILURE: #{@message})\e[0m"
30
+ end
31
+ end
32
+
33
+ # Helper used to match individual values against each other. If the values
34
+ # are arrays or hashes, we delegate to match_arrays or match_hashes.
35
+ # @param expected [Object] expected values or matchers
36
+ # @param actual [Object] actual data
37
+ # @return [Array(Boolean, Object)] a tuple whose first member is a Boolean
38
+ # representing whether the match succeeded and whose second member is
39
+ # something which, when `pretty_inspect` is called on it, will return a
40
+ # string representing the original value where the match succeeded or
41
+ # describing the failure where the match failed.
42
+ def self.match_values(expected, actual)
43
+ if Array === expected && Enumerable === actual
44
+ return match_arrays(expected, actual.to_a)
45
+ end
46
+
47
+ if Hash === expected && Hash === actual
48
+ return match_hashes(expected, actual)
49
+ end
50
+
51
+ begin
52
+ did_match = (actual == expected || expected === actual)
53
+ rescue ArgumentError
54
+ # Some objects, like 0-arg lambdas on 1.9+, raise
55
+ # ArgumentError for `expected === actual`.
56
+ false
57
+ end
58
+
59
+ if did_match
60
+ [true, actual]
61
+ else
62
+ [false, extract_results_with_errors(expected, actual)]
63
+ end
64
+ end
65
+
66
+ # Helper used to match arrays against each other.
67
+ # @param expected_list [Array] expected values or matchers
68
+ # @param actual_list [Array] actual data
69
+ # @return [Array(Boolean, Array)] a tuple whose first member is a
70
+ # Boolean representing whether the match succeeded and whose second
71
+ # member is something which, when `pretty_inspect` is called on it, will
72
+ # return a string representing the original value where the match
73
+ # succeeded or describing the failure where the match failed.
74
+ def self.match_arrays(expected_list, actual_list)
75
+ all_matched = true
76
+ result_list = []
77
+
78
+ # For indexes that are present in both lists, match the values against
79
+ # their respective expectations.
80
+ expected_list.take(actual_list.length).each_with_index do |expected, idx|
81
+ actual = actual_list[idx]
82
+ value_matched, value = match_values(expected, actual)
83
+ all_matched &&= value_matched
84
+ result_list << value
85
+ end
86
+
87
+ # If the expected list was longer, add "was absent" errors.
88
+ expected_list.drop(actual_list.length).each do |expected|
89
+ all_matched = false
90
+ result_list << failed_match_message(expected, 'absent')
91
+ end
92
+
93
+ # If the actual list was longer, add "should have been absent" errors.
94
+ actual_list.drop(expected_list.length).each do |actual|
95
+ all_matched = false
96
+ result_list << extra_key_message(actual.inspect)
97
+ end
98
+
99
+ [all_matched, result_list]
100
+ end
101
+
102
+ # Helper used to match hashes against each other. Also checks for the
103
+ # existence of unexpected keys and the absence of expected keys.
104
+ # @param expected_hash [Hash] expected values or matchers
105
+ # @param actual_hash [Hash] actual data
106
+ # @return [Array(Boolean, Hash)] a tuple whose first member is a Boolean
107
+ # representing whether the match succeeded and whose second member is
108
+ # something which, when `pretty_inspect` is called on it, will return a
109
+ # string representing the original value where the match succeeded or
110
+ # describing the failure where the match failed.
111
+ def self.match_hashes(expected_hash, actual_hash)
112
+ all_matched = true
113
+ result_hash = {}
114
+
115
+ # Stringify expected keys so that we can use new-style hashes in our
116
+ # specs.
117
+ expected_hash = expected_hash.stringify_keys
118
+
119
+ # Errors for missing keys, or extra keys are created at the hash level
120
+ # because it is difficult to detect at the element level whether or not a
121
+ # `nil` value is caused by a missing key, or an actual `nil` value.
122
+ expected_hash.each do |expected_key, expected_value|
123
+ if actual_hash.key?(expected_key)
124
+ actual_value = actual_hash[expected_key]
125
+ value_matched, value = match_values(expected_value, actual_value)
126
+ all_matched &&= value_matched
127
+ elsif AbsenceMatcher.is_an_absence_matcher?(expected_value)
128
+ # We expected the value to not be present, and it isn't, so we're all
129
+ # good.
130
+ else
131
+ # Mark any missing keys as failures
132
+ value = failed_match_message(expected_value, 'absent')
133
+ all_matched = false
134
+ end
135
+
136
+ result_hash[expected_key] = value
137
+ end
138
+
139
+ # If there are extra keys, we should mark them as invalid
140
+ (actual_hash.keys - expected_hash.keys).each do |key|
141
+ result_hash[key] = extra_key_message(actual_hash[key].inspect)
142
+ all_matched = false
143
+ end
144
+
145
+ [all_matched, result_hash]
146
+ end
147
+
148
+ def self.extract_results_with_errors(expected, actual)
149
+ if expected.respond_to?(:results_with_errors)
150
+ return expected.results_with_errors
151
+ end
152
+
153
+ case expected
154
+ when RSpec::Matchers::BuiltIn::Compound::Or
155
+ extract_results_from_or_matcher(expected, actual)
156
+ when RSpec::Matchers::BuiltIn::All
157
+ extract_results_from_all_matcher(expected, actual)
158
+ else
159
+ failed_match_message(expected, actual.inspect)
160
+ end
161
+ end
162
+
163
+ # Check both branches for structured results before falling back to the
164
+ # matcher's description method.
165
+ def self.extract_results_from_or_matcher(expected, actual)
166
+ [expected.matcher_1, expected.matcher_2].each do |matcher|
167
+ result = extract_results_with_errors(matcher, actual)
168
+ return result unless result.is_a?(FailureDescription)
169
+ end
170
+
171
+ failed_match_message(expected, actual.inspect)
172
+ end
173
+
174
+ # To deal with the `all` matcher, we need to rerun the matcher on each
175
+ # element so that we can then try to extract results from it.
176
+ def self.extract_results_from_all_matcher(expected, actual)
177
+ unless actual.respond_to?(:map)
178
+ return failed_match_message(expected, actual.inspect)
179
+ end
180
+
181
+ actual.map do |actual_item|
182
+ cloned_matcher = expected.matcher.clone
183
+ matches = cloned_matcher.matches?(actual_item)
184
+
185
+ if matches
186
+ actual_item
187
+ else
188
+ extract_results_with_errors(cloned_matcher, actual_item)
189
+ end
190
+ end
191
+ end
192
+
193
+ # Generate a failure description based on expected and actual values.
194
+ # @param expected [Object] expected matcher or value
195
+ # @param actual [Object] actual value
196
+ # @return [FailureDescription]
197
+ def self.failed_match_message(expected, actual)
198
+ description = surface_descriptions_in(expected).inspect
199
+ FailureDescription.new("was #{actual}, should have been #{description}")
200
+ end
201
+
202
+ # Generate a failure description for a key that should not have been
203
+ # present.
204
+ # @param actual_value [Object]
205
+ # @return [FailureDescription]
206
+ def self.extra_key_message(actual_value)
207
+ FailureDescription.new("was #{actual_value}, should have been absent")
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,34 @@
1
+ module RSpecJsonMatchers
2
+ # Helper class that powers our DSL for defining API matchers.
3
+ class JsonMatcherDefinition < BasicObject
4
+ def initialize
5
+ @attrs = {}
6
+ end
7
+
8
+ # Defines a matcher for the specified field.
9
+ # @param method_name [Symbol] Name of the field to match against
10
+ # @block Block which, when called, returns matcher used to verify actual
11
+ # data
12
+ # @example
13
+ # id { a_kind_of Integer } # the id attribute should be a number
14
+ # rubocop:disable Style/MethodMissing
15
+ # disabled since method existence can depend on args / block
16
+ def method_missing(name, *args, &block)
17
+ if args.length > 0 || block.nil?
18
+ super
19
+ else
20
+ @attrs[name.to_s] = block
21
+ end
22
+ end
23
+ # rubocop:enable Style/MethodMissing
24
+
25
+ # @param matcher_context [Object] The context in which to run the matcher
26
+ # procs. This should be the spec instance.
27
+ # @return [Hash] of field name, matcher pairs to be used for validation
28
+ def to_hash(matcher_context)
29
+ ::Hash[@attrs.map do |name, matcher_proc|
30
+ [name, matcher_context.instance_exec(&matcher_proc)]
31
+ end]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,81 @@
1
+ require 'rspec_json_matchers/json_matcher_definition'
2
+ require 'rspec_json_matchers/api_response_matcher'
3
+ require 'rspec_json_matchers/fuzzy_matcher'
4
+ require 'rspec_json_matchers/absence_matcher'
5
+
6
+ require 'active_support/core_ext/hash/reverse_merge'
7
+ require 'active_support/core_ext/hash/keys'
8
+
9
+ # This module defines matchers that are useful in writing request specs for our
10
+ # API. Specifically, they make it easy to specify the structure of the entire
11
+ # response, taking advantage of the fact that our API is mostly made up of
12
+ # objects that should be consistent in their structure anywhere that they
13
+ # appear.
14
+ #
15
+ # For example, a "profile" is a type of API object that has the same structure
16
+ # whether it's appearing in the author field of a comment or the profile field
17
+ # of the /me endpoint. We can use the `define_api_matcher` method to define
18
+ # matchers called `a_serialized_profile` and `be_a_serialized_profile` which
19
+ # will match successfully against any serialized profile. If the user passes a
20
+ # hash into the matcher, they can customize it by specifying specific values it
21
+ # should include or using any RSpec matcher to narrow down the range of
22
+ # acceptable values for a given field. They can also use the `absent`
23
+ # matcher to assert that a given key should *not* be included in the response.
24
+ #
25
+ # @see spec/api_matchers/profile.rb for an example of an API matcher.
26
+ module RSpecJsonMatchers
27
+ # @param [Symbol]
28
+ # @yield [] declarations for expected fields and matcher pairs
29
+ def self.define_api_matcher(name, &block)
30
+ definition = JsonMatcherDefinition.new
31
+ definition.instance_eval(&block)
32
+
33
+ # @param [expected] a hash of values/matchers with which to customize this
34
+ # API matcher instance.
35
+ # @return [ApiResponseMatcher] a matcher based on the provided name,
36
+ # definition block, and hash of expected values/matchers.
37
+ define_method "a_serialized_#{name}" do |expected = {}|
38
+ ApiResponseMatcher.new(
39
+ expected,
40
+ object_definition: definition,
41
+ object_name: name.to_s,
42
+ context: self
43
+ )
44
+ end
45
+ alias_method "be_a_serialized_#{name}", "a_serialized_#{name}"
46
+ end
47
+
48
+ # This matcher validates all or part of an API response against a schema. If
49
+ # the response violates the schema, it shows which parts of the response
50
+ # failed to match our expectations.
51
+ # @param expected [Hash] a hash representing an API response. Its values can
52
+ # be matchers (like `an_instance_of(String)`) or concrete values (like
53
+ # `10`).
54
+ # @return [ApiResponseMatcher]
55
+ def match_api_response(expected)
56
+ ApiResponseMatcher.new(expected)
57
+ end
58
+ alias_method 'a_hash_matching_api_response', 'match_api_response'
59
+
60
+ # This matcher works like `match_api_response`, but also verifies that
61
+ # response matches `meta: a_serialized_pagination_metadata_hash`. This
62
+ # prevents that line from needing to be repeated in every spec of a paginated
63
+ # endpoint.
64
+ def match_paginated_api_response(expected)
65
+ match_api_response(
66
+ expected.merge(meta: a_serialized_pagination_metadata_hash)
67
+ )
68
+ end
69
+ alias_method 'a_hash_matching_paginated_api_response',
70
+ 'match_paginated_api_response'
71
+
72
+ # @return [AbsenceMatcher] a matcher indicating that a key shouldn't be
73
+ # present in an API response. Only useful in combination with
74
+ # ApiResponseMatcher.
75
+ def absent
76
+ AbsenceMatcher.new
77
+ end
78
+ end
79
+
80
+
81
+ # Dir[Rails.root.join('spec/api_matchers/**/*.rb')].each { |f| require f }
@@ -0,0 +1,3 @@
1
+ RSpecJsonMatchers.define_api_matcher :absent do
2
+ absent { absent }
3
+ end
@@ -0,0 +1,16 @@
1
+ puts :doing_this
2
+
3
+ RSpecJsonMatchers.define_api_matcher :matcher_test_object do
4
+ numeric_type { a_kind_of(Integer) }
5
+ string_type { an_instance_of(String) }
6
+ numeric_value { numeric_value }
7
+ string_value { string_value }
8
+ absent_value { absent }
9
+ nil_value { a_nil_value }
10
+ composed_matcher { a_nil_value.or(an_instance_of(String)) }
11
+ another_matcher { a_serialized_matcher_another_object }
12
+ end
13
+
14
+ RSpecJsonMatchers.define_api_matcher :matcher_another_object do
15
+ foo { an_instance_of(String) }
16
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RSpecJsonMatchers::AbsenceMatcher do
4
+ # See `spec/json_matchers/absence_matcher_objects.rb` for matcher definitions
5
+
6
+ let(:valid_hash) { {} }
7
+ let(:invalid_hash) { { absent: :not_absent } }
8
+
9
+ it 'matches absent keys' do
10
+ expect(valid_hash).to match_api_response(a_serialized_absent)
11
+ end
12
+
13
+ it 'detects a non-absent key' do
14
+ expect(invalid_hash).to_not be_a_serialized_absent
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RSpecJsonMatchers do
4
+ # See `spec/json_matchers/rspec_json_matchers_objects.rb` for matcher
5
+ # definitions
6
+
7
+ let(:string_value) { 'bar' }
8
+ let(:numeric_value) { 2 }
9
+
10
+ let(:valid_hash) do
11
+ {
12
+ 'numeric_type' => 1,
13
+ 'string_type' => 'foo',
14
+ 'numeric_value' => numeric_value,
15
+ 'string_value' => string_value,
16
+ # absent_value
17
+ 'nil_value' => nil,
18
+ 'composed_matcher' => 'not nil but can be nil',
19
+ 'another_matcher' => {
20
+ 'foo' => 'bar'
21
+ }
22
+ }
23
+ end
24
+
25
+ let(:serialized_another_object) { { 'foo' => 'bar' } }
26
+
27
+ let(:invalid_hash) { { absent: :not_absent } }
28
+
29
+ it 'matches absent keys' do
30
+ expect(valid_hash).to match_api_response(a_serialized_matcher_test_object)
31
+ end
32
+
33
+ it 'detects a non-absent key' do
34
+ expect(invalid_hash).to_not be_a_serialized_matcher_test_object
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec_json_matchers'
2
+
3
+ RSpec.configure do |config|
4
+ config.include RSpecJsonMatchers
5
+ end
6
+
7
+ Dir[File.dirname(__FILE__) + '/json_matchers/**/*.rb'].each { |f| require f }
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec_json_matchers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Fitzgerald
8
+ - Hao Su
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-01-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.0'
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: 4.0.2
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - "~>"
43
+ - !ruby/object:Gem::Version
44
+ version: '4.0'
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 4.0.2
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: activesupport
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 4.0.2
72
+ type: :runtime
73
+ prerelease: false
74
+ version_requirements: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - "~>"
77
+ - !ruby/object:Gem::Version
78
+ version: '4.0'
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 4.0.2
82
+ description: JSON matchers for RSpec
83
+ email:
84
+ - rwfitzge@gmail.com
85
+ - me@haosu.org
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - Gemfile
91
+ - LICENSE
92
+ - README.md
93
+ - lib/rspec_json_matchers.rb
94
+ - lib/rspec_json_matchers/absence_matcher.rb
95
+ - lib/rspec_json_matchers/api_response_matcher.rb
96
+ - lib/rspec_json_matchers/fuzzy_matcher.rb
97
+ - lib/rspec_json_matchers/json_matcher_definition.rb
98
+ - spec/json_matchers/absence_matcher_objects.rb
99
+ - spec/json_matchers/rspec_json_matchers_objects.rb
100
+ - spec/lib/rspec_json_matchers/absence_matcher_spec.rb
101
+ - spec/lib/rspec_json_matchers_spec.rb
102
+ - spec/spec_helper.rb
103
+ homepage: https://github.com/brigade/rspec_json_matchers/
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.5.1
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: RSpec JSON Matchers
127
+ test_files: []