rspec-json_matchers 0.1.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +20 -0
  4. data/Appraisals +16 -0
  5. data/CHANGELOG.md +7 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +497 -0
  9. data/Rakefile +14 -0
  10. data/gemfiles/rspec_3_0.gemfile +7 -0
  11. data/gemfiles/rspec_3_1.gemfile +7 -0
  12. data/gemfiles/rspec_3_2.gemfile +7 -0
  13. data/gemfiles/rspec_3_3.gemfile +7 -0
  14. data/lib/rspec/json_matchers/comparers/abstract_comparer.rb +289 -0
  15. data/lib/rspec/json_matchers/comparers/comparison_result.rb +22 -0
  16. data/lib/rspec/json_matchers/comparers/exact_keys_comparer.rb +27 -0
  17. data/lib/rspec/json_matchers/comparers/include_keys_comparer.rb +27 -0
  18. data/lib/rspec/json_matchers/comparers.rb +13 -0
  19. data/lib/rspec/json_matchers/expectation.rb +78 -0
  20. data/lib/rspec/json_matchers/expectations/abstract.rb +36 -0
  21. data/lib/rspec/json_matchers/expectations/core.rb +103 -0
  22. data/lib/rspec/json_matchers/expectations/mixins/built_in.rb +177 -0
  23. data/lib/rspec/json_matchers/expectations/private.rb +181 -0
  24. data/lib/rspec/json_matchers/expectations.rb +14 -0
  25. data/lib/rspec/json_matchers/matchers/be_json_matcher.rb +92 -0
  26. data/lib/rspec/json_matchers/matchers/be_json_with_content_matcher.rb +21 -0
  27. data/lib/rspec/json_matchers/matchers/be_json_with_sizes_matcher.rb +21 -0
  28. data/lib/rspec/json_matchers/matchers/be_json_with_something_matcher.rb +174 -0
  29. data/lib/rspec/json_matchers/matchers.rb +12 -0
  30. data/lib/rspec/json_matchers/utils/collection_keys_extractor.rb +35 -0
  31. data/lib/rspec/json_matchers/utils/key_path/extraction_result.rb +22 -0
  32. data/lib/rspec/json_matchers/utils/key_path/extractor.rb +70 -0
  33. data/lib/rspec/json_matchers/utils/key_path/path.rb +104 -0
  34. data/lib/rspec/json_matchers/utils.rb +10 -0
  35. data/lib/rspec/json_matchers/version.rb +8 -0
  36. data/lib/rspec/json_matchers.rb +15 -0
  37. data/lib/rspec-json_matchers.rb +1 -0
  38. data/rspec-json_matchers.gemspec +47 -0
  39. metadata +245 -0
@@ -0,0 +1,177 @@
1
+ require "abstract_class"
2
+
3
+ require_relative "../core"
4
+ require_relative "../abstract"
5
+
6
+ module RSpec
7
+ module JsonMatchers
8
+ module Expectations
9
+ # @api
10
+ # The modules under this module can be included (in RSpec)
11
+ #
12
+ # If this gem or extensions gems decide to add different groups of expectations classes
13
+ # Which aim to be included in example groups
14
+ # They should add the namespace modules here
15
+ module Mixins
16
+ # @api
17
+ # All classes within module should be able to be used / extended
18
+ #
19
+ # A group of expectation classes provided by this gem
20
+ # Other extension gems (if any) should create another namespace
21
+ # if they intend to provide extra expectation classes
22
+ module BuiltIn
23
+ # Whatever the value is, it just passes
24
+ # A more verbose solution than passing {Object} in
25
+ # (That also works since everything parsed by {JSON} inherits from {Object})
26
+ #
27
+ # @example
28
+ # { key_with_unstable_content => Anything }
29
+ class Anything < Expectations::Core::SingletonExpectation
30
+ def expect?(*_args)
31
+ true
32
+ end
33
+ end
34
+
35
+ # Checks the value is a {Numeric} & less then zero
36
+ #
37
+ # @note (see Expectations::Private::NumericExpectation)
38
+ class PositiveNumber < Expectations::Abstract::NumericExpectation
39
+ def expect?(value)
40
+ super && value > 0
41
+ end
42
+ end
43
+
44
+ # Checks the value is a {Numeric} & less then zero
45
+ #
46
+ # @note (see Expectations::Private::NumericExpectation)
47
+ class NegativeNumber < Expectations::Abstract::NumericExpectation
48
+ def expect?(value)
49
+ super && value < 0
50
+ end
51
+ end
52
+
53
+ # Checks the value is a {TrueClass} or {FalseClass}
54
+ #
55
+ # @note
56
+ # The class does use name Boolean since so many gems uses it already
57
+ # You can also use gems like https://github.com/janlelis/boolean2/
58
+ class BooleanValue < Expectations::Core::SingletonExpectation
59
+ def expect?(value)
60
+ true == value || false == value
61
+ end
62
+ end
63
+
64
+ # Takes exactly one object and converts to an expectation object (if not already)
65
+ # Validates `value` to be {Array}
66
+ # And uses stored expectation for checking all elements of `value`
67
+ class ArrayOf < Expectations::Core::SingleValueCallableExpectation
68
+ private
69
+ attr_reader :children_elements_expectation
70
+ public
71
+
72
+ def expect?(value)
73
+ value.is_a?(Array) &&
74
+ (empty_allowed? || !value.empty?) &&
75
+ value.all? {|v| children_elements_expectation.expect?(v) }
76
+ end
77
+
78
+ # {Enumerable#all?} returns `true` when collection is empty
79
+ # So this method can be called to signal the expectation to do or do not expect an empty collection
80
+ #
81
+ # @param allow [Boolean]
82
+ # optional
83
+ # Should empty collection be "expected"
84
+ #
85
+ # @return [ArrayOf] the matcher itself
86
+ def allow_empty(allow = true)
87
+ @empty_allowed = !!allow
88
+ self
89
+ end
90
+
91
+ # A more verbose alias for `allow_empty(false)`
92
+ #
93
+ # @return (see #allow_empty)
94
+ def disallow_empty
95
+ allow_empty(false)
96
+ end
97
+
98
+ private
99
+
100
+ def initialize(value)
101
+ @children_elements_expectation = Expectation.build(value)
102
+ @empty_allowed = true
103
+ end
104
+
105
+ def empty_allowed?
106
+ !!@empty_allowed
107
+ end
108
+ end
109
+
110
+ # (see CompositeExpectation)
111
+ # It passes when any of expectation returns true
112
+ class AnyOf < Expectations::Core::CompositeExpectation
113
+ def expect?(value)
114
+ expectations.any? do |expectation|
115
+ expectation.expect?(value)
116
+ end
117
+ end
118
+ end
119
+
120
+ # (see CompositeExpectation)
121
+ # It passes when all of expectations return true
122
+ class AllOf < Expectations::Core::CompositeExpectation
123
+ def expect?(value)
124
+ expectations.all? do |expectation|
125
+ expectation.expect?(value)
126
+ end
127
+ end
128
+ end
129
+
130
+ # Takes any number of {Integer} or {Range} (if not already)
131
+ # Validates `value` to be {Array}
132
+ # And the size matches any value passed in
133
+ #
134
+ # @note
135
+ # For behaviour of "and" (which should be a rare case)
136
+ # Combine {AllOf} & {ArrayWithSize}
137
+ # Or raise an issue to add support for switching to "and" with another method call
138
+ class ArrayWithSize < AnyOf
139
+ # `Fixnum` & `Bignum` will be returned instead of `Integer`
140
+ # in `#class` for numbers
141
+ EXPECTED_VALUE_CLASS_TO_EXPECTATION_CLASS_MAPPING = {
142
+ Fixnum => -> (v) { Expectations::Private::Eq[v] },
143
+ Bignum => -> (v) { Expectations::Private::Eq[v] },
144
+ Range => -> (v) { Expectations::Private::InRange[v] },
145
+ }.freeze
146
+ private_constant :EXPECTED_VALUE_CLASS_TO_EXPECTATION_CLASS_MAPPING
147
+
148
+ class << self
149
+ # Overrides {Expectation.build}
150
+ def build(value)
151
+ expectation_classes_mappings.fetch(value.class) do
152
+ -> (_) { raise ArgumentError, <<-ERR }
153
+ Expected expection(s) to be kind of
154
+ #{expectation_classes_mappings.keys.inspect}
155
+ but found #{value.inspect}
156
+ ERR
157
+ end.call(value)
158
+ end
159
+
160
+ private
161
+
162
+ # @return [Hash]
163
+ def expectation_classes_mappings
164
+ EXPECTED_VALUE_CLASS_TO_EXPECTATION_CLASS_MAPPING
165
+ end
166
+ end
167
+
168
+ def expect?(value)
169
+ value.is_a?(Array) &&
170
+ super(value.size)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,181 @@
1
+ require "abstract_class"
2
+
3
+ require_relative "core"
4
+ require_relative "mixins/built_in"
5
+
6
+ module RSpec
7
+ module JsonMatchers
8
+ module Expectations
9
+ # @api private
10
+ # All classes within module should NOT be able to be used directly / extended
11
+ #
12
+ # All classes in this module are internal expectations used when non-expectation object/class is passed in
13
+ # Extension gems should have their own namespace and should NOT add new classes to this namespace
14
+ # Classes here have dependency on {Core} & {Mixins::BuiltIn}
15
+ #
16
+ # TODO: Remove dependency on {Mixins::BuiltIn}
17
+ module Private
18
+ # @api private
19
+ # User should just pass an object in
20
+ #
21
+ # Takes exactly one object
22
+ # Use stored value & `==` for checking `value`
23
+ class Eq < Core::SingleValueCallableExpectation
24
+ private
25
+ attr_reader :expected_value
26
+ public
27
+
28
+ def expect?(value)
29
+ value == expected_value
30
+ end
31
+
32
+ private
33
+
34
+ def initialize(value)
35
+ @expected_value = value
36
+ end
37
+ end
38
+
39
+ # @api private
40
+ # User should just pass a class in
41
+ #
42
+ # Takes exactly one object
43
+ # Use stored class for checking `value`
44
+ #
45
+ # @note
46
+ # Might use a whitelist of acceptable classes
47
+ # and raise error if other things passed in
48
+ # in the future
49
+ class KindOf < Core::SingleValueCallableExpectation
50
+ EXPECTED_CLASS = Class
51
+ private_constant :EXPECTED_CLASS
52
+
53
+ private
54
+ attr_reader :expected_class
55
+ public
56
+
57
+ def expect?(value)
58
+ value.is_a?(expected_class)
59
+ end
60
+
61
+ private
62
+
63
+ def initialize(value)
64
+ raise ArgumentError, "a #{EXPECTED_CLASS} is required" unless value.is_a?(EXPECTED_CLASS)
65
+ @expected_class = value
66
+ end
67
+ end
68
+
69
+ # @api private
70
+ # User should just pass a {Range} in
71
+ #
72
+ # Takes exactly one object
73
+ # Use stored proc for checking `value`
74
+ class InRange < Core::SingleValueCallableExpectation
75
+ EXPECTED_CLASS = Range
76
+ private_constant :EXPECTED_CLASS
77
+
78
+ private
79
+ attr_reader :range
80
+ public
81
+
82
+ def expect?(value)
83
+ range.cover?(value)
84
+ end
85
+
86
+ private
87
+
88
+ def initialize(value)
89
+ raise ArgumentError, "a #{EXPECTED_CLASS} is required" unless value.is_a?(EXPECTED_CLASS)
90
+ @range = value
91
+ end
92
+ end
93
+
94
+ # @api private
95
+ # User should just pass a {Regexp} in
96
+ #
97
+ # Takes exactly one object
98
+ # Use stored regexp for checking `value`
99
+ class MatchingRegexp < Core::SingleValueCallableExpectation
100
+ EXPECTED_CLASS = Regexp
101
+ private_constant :EXPECTED_CLASS
102
+
103
+ private
104
+ attr_reader :regexp
105
+ public
106
+
107
+ def expect?(value)
108
+ # regex =~ string seems to be fastest
109
+ # @see https://stackoverflow.com/questions/11887145/fastest-way-to-check-if-a-string-matches-or-not-a-regexp-in-ruby
110
+ value.is_a?(String) && !!(regexp =~ value)
111
+ end
112
+
113
+ private
114
+
115
+ def initialize(value)
116
+ raise ArgumentError, "a #{EXPECTED_CLASS} is required" unless value.is_a?(EXPECTED_CLASS)
117
+ @regexp = value
118
+ end
119
+ end
120
+
121
+ # @api private
122
+ # User should just pass a callable in
123
+ #
124
+ # Takes exactly one object
125
+ # Use stored proc for checking `value`
126
+ class SatisfyingCallable < Core::SingleValueCallableExpectation
127
+ private
128
+ attr_reader :callable
129
+ public
130
+
131
+ def expect?(value)
132
+ callable.call(value)
133
+ end
134
+
135
+ private
136
+
137
+ def initialize(value)
138
+ raise ArgumentError, "an object which respond to `:call` is required" unless value.respond_to?(:call)
139
+ @callable = value
140
+ end
141
+ end
142
+
143
+ # @api private
144
+ # Used internally for returning false
145
+ #
146
+ # Always "fail"
147
+ class Nothing < Expectations::Core::SingletonExpectation
148
+ def expect?(*_args)
149
+ false
150
+ end
151
+ end
152
+
153
+ # @api private
154
+ # Used internally by a matcher method
155
+ #
156
+ # Comparing to {Expectations::Mixins::BuiltIn::ArrayWithSize}
157
+ # This also accepts `Hash` and `Array`, and return false for collection matching
158
+ class ArrayWithSize < Expectations::Mixins::BuiltIn::ArrayWithSize
159
+ # `Fixnum` & `Bignum` will be returned instead of `Integer`
160
+ # in `#class` for numbers
161
+ ADDITIONAL_EXPECTED_VALUE_CLASS_TO_EXPECTATION_CLASS_MAPPING = {
162
+ Array => -> (_) { Expectations::Private::Nothing::INSTANCE },
163
+ Hash => -> (_) { Expectations::Private::Nothing::INSTANCE },
164
+ }.freeze
165
+ private_constant :ADDITIONAL_EXPECTED_VALUE_CLASS_TO_EXPECTATION_CLASS_MAPPING
166
+
167
+ class << self
168
+ private
169
+
170
+ # Overrides {Expectations::Mixins::BuiltIn::ArrayWithSize.expectation_classes_mappings}
171
+ #
172
+ # @return [Hash]
173
+ def expectation_classes_mappings
174
+ super.merge(ADDITIONAL_EXPECTED_VALUE_CLASS_TO_EXPECTATION_CLASS_MAPPING)
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,14 @@
1
+ require_relative "expectations/core"
2
+ require_relative "expectations/private"
3
+ require_relative "expectations/mixins/built_in"
4
+
5
+ module RSpec
6
+ module JsonMatchers
7
+ # This module does not mean to have any expectation class
8
+ # Use the structure like {Expectations::BuiltIn}
9
+ #
10
+ # @api private
11
+ module Expectations
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,92 @@
1
+ require "json"
2
+
3
+ module RSpec
4
+ module JsonMatchers
5
+ module Matchers
6
+ # @api
7
+ #
8
+ # Used for verifying actual is a valid JSON string
9
+ #
10
+ # @return [BeJsonMatcher]
11
+ def be_json
12
+ BeJsonMatcher.new
13
+ end
14
+
15
+ # @api private
16
+ class BeJsonMatcher
17
+ attr_reader :actual
18
+
19
+ def matches?(json)
20
+ @actual = JSON.parse(json)
21
+ true
22
+ rescue JSON::ParserError
23
+ @has_parser_error = true
24
+ false
25
+ end
26
+
27
+ def does_not_match?(*args)
28
+ !matches?(*args)
29
+ end
30
+
31
+ # @api
32
+ #
33
+ # Get a matcher that try to match the content of actual
34
+ # with nested various expectations
35
+ #
36
+ # @param expected [Hash, Array, Object]
37
+ # the expectation object
38
+ #
39
+ # @return [BeJsonWithContentMatcher] a matcher object
40
+ def with_content(expected)
41
+ BeJsonWithContentMatcher.new(expected)
42
+ end
43
+
44
+ # @api
45
+ #
46
+ # Get a matcher that try to match the content of actual
47
+ # with nested expectations about array sizes
48
+ #
49
+ # @param expected [Hash, Array, Object]
50
+ # the expectation object
51
+ #
52
+ # @return [BeJsonWithSizesMatcher] a matcher object
53
+ def with_sizes(expected)
54
+ BeJsonWithSizesMatcher.new(expected)
55
+ end
56
+
57
+ # Expectation description in spec result summary
58
+ #
59
+ # @return [String]
60
+ def description
61
+ "be a valid JSON string"
62
+ end
63
+
64
+ # Failure message displayed when a positive example failed (e.g. using `should`)
65
+ #
66
+ # @return [String]
67
+ def failure_message_for_positive
68
+ "expected value to be parsed as JSON, but failed"
69
+ end
70
+ alias :failure_message :failure_message_for_positive
71
+
72
+ # Failure message displayed when a negative example failed (e.g. using `should_not`)
73
+ #
74
+ # @return [String]
75
+ def failure_message_for_negative
76
+ "expected value not to be parsed as JSON, but succeeded"
77
+ end
78
+ alias :failure_message_when_negated :failure_message_for_negative
79
+
80
+ private
81
+
82
+ def has_parser_error?
83
+ !!@has_parser_error
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ # These files are required since the classes are only required on runtime, not load time
91
+ require_relative "be_json_with_content_matcher"
92
+ require_relative "be_json_with_sizes_matcher"
@@ -0,0 +1,21 @@
1
+ require "json"
2
+ require "awesome_print"
3
+
4
+ require_relative "be_json_with_something_matcher"
5
+ require_relative "../comparers"
6
+ require_relative "../utils"
7
+
8
+ module RSpec
9
+ module JsonMatchers
10
+ module Matchers
11
+ # @api private
12
+ class BeJsonWithContentMatcher < BeJsonWithSomethingMatcher
13
+ private
14
+
15
+ def value_matching_proc
16
+ -> (expected, actual) { Expectation.build(expected).expect?(actual) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require "json"
2
+ require "awesome_print"
3
+
4
+ require_relative "be_json_with_something_matcher"
5
+ require_relative "../comparers"
6
+ require_relative "../utils"
7
+
8
+ module RSpec
9
+ module JsonMatchers
10
+ module Matchers
11
+ # @api private
12
+ class BeJsonWithSizesMatcher < BeJsonWithSomethingMatcher
13
+ private
14
+
15
+ def value_matching_proc
16
+ -> (expected, actual) { Expectations::Private::ArrayWithSize[expected].expect?(actual) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,174 @@
1
+ require "json"
2
+ require "awesome_print"
3
+ require "abstract_class"
4
+
5
+ require_relative "be_json_matcher"
6
+ require_relative "../utils"
7
+
8
+ module RSpec
9
+ module JsonMatchers
10
+ module Matchers
11
+ # @api private
12
+ # @abstract
13
+ #
14
+ # Parent of matcher classes that requires {#at_path} & {#with_exact_keys}
15
+ # This is not merged with {BeJsonMatcher} since it should be able to be used alone
16
+ class BeJsonWithSomethingMatcher < BeJsonMatcher
17
+ extend AbstractClass
18
+
19
+ attr_reader *[
20
+ :expected,
21
+ :path,
22
+ :with_exact_keys,
23
+ ]
24
+ alias_method :with_exact_keys?, :with_exact_keys
25
+
26
+ def initialize(expected)
27
+ @expected = expected
28
+ @path = JsonMatchers::Utils::KeyPath::Path.new("")
29
+ @exact_match = false
30
+ end
31
+
32
+ def matches?(*_args)
33
+ super && has_valid_path? && expected_and_actual_matched?
34
+ end
35
+
36
+ def does_not_match?(*args)
37
+ !matches?(*args) && has_valid_path?
38
+ end
39
+
40
+ def description
41
+ super
42
+ end
43
+
44
+ # Override {BeJsonMatcher#actual}
45
+ # It return actual object extracted by {#path}
46
+ # And also detect & set state for path error (either it's invalid or fails to extract)
47
+ #
48
+ # @return [Object] extracted object but could be object in the middle when extraction failed
49
+ def actual
50
+ result = path.extract(super)
51
+ has_path_error! if result.failed?
52
+ result.object
53
+ end
54
+
55
+ # Sets the path to be used for object, to avoid passing a deep nested {Hash} or {Array} as expectation
56
+ # Defaults to "" (if this is not called)
57
+ # The path uses period (".") as separator for parts
58
+ # (also period cannot be used as path name as a side-effect, but who does?)
59
+ # This does NOT raise error if the path is invalid
60
+ # (like having 2 periods, 1 period at the start/end of string)
61
+ # But it will fail the example with both `should` & `should_not`
62
+ #
63
+ # @param path [String] the "path" to be used
64
+ #
65
+ # @return [BeJsonWithSomethingMatcher] the match itself
66
+ #
67
+ # @throw [TypeError] when input is not a string
68
+ def at_path(path)
69
+ @path = JsonMatchers::Utils::KeyPath::Path.new(path)
70
+ self
71
+ end
72
+
73
+ # When `exactly` is `true`,
74
+ # makes the matcher to fail the example
75
+ # when actual has more elements than expected even expectation passes
76
+ #
77
+ # When `exactly` is `true`,
78
+ # makes the matcher to pass the example
79
+ # when actual has more elements than expected and expectation passes
80
+ #
81
+ # @param exactly [Boolean] whether the matcher should match keys in actual & expected exactly
82
+ #
83
+ # @return (see #at_path)
84
+ def with_exact_keys(exactly = true)
85
+ @with_exact_keys = !!exactly
86
+ self
87
+ end
88
+
89
+ def failure_message_for_positive
90
+ return super if has_parser_error?
91
+ return invalid_path_message unless has_valid_path?
92
+ return path_error_message if has_path_error?
93
+
94
+ inspection_messages(true)
95
+ end
96
+ alias :failure_message :failure_message_for_positive
97
+
98
+ def failure_message_for_negative
99
+ return super if has_parser_error?
100
+ return invalid_path_message unless has_valid_path?
101
+ return path_error_message if has_path_error?
102
+
103
+ inspection_messages(false)
104
+ end
105
+ alias :failure_message_when_negated :failure_message_for_negative
106
+
107
+ private
108
+
109
+ # @return [Bool] Whether `expected` & `parsed` are "equal"
110
+ def expected_and_actual_matched?
111
+ extracted_actual = actual
112
+ return false if has_path_error?
113
+ result = comparer_klass.new(extracted_actual, expected, reasons, value_matching_proc).compare
114
+
115
+ result.matched?.tap do |matched|
116
+ @reasons = result.reasons unless matched
117
+ end
118
+ end
119
+
120
+ def reasons
121
+ @reasons ||= []
122
+ end
123
+
124
+ def inspection_messages(should_match)
125
+ prefix = !!should_match ? nil : "not"
126
+
127
+ messages = [
128
+ ["expected", prefix, "to match:"].compact.map(&:strip).join(" "),
129
+ expected.awesome_inspect(indent: -2),
130
+ "",
131
+ "actual:",
132
+ actual.awesome_inspect(indent: -2),
133
+ "",
134
+ ]
135
+ messages.push "reason/path: #{reasons.reverse.join(".")}" unless reasons.empty?
136
+ messages.join("\n")
137
+ end
138
+
139
+ def original_actual
140
+ @actual
141
+ end
142
+
143
+ def has_path_error?
144
+ !!@has_path_error
145
+ end
146
+
147
+ def has_path_error!
148
+ @has_path_error = true
149
+ end
150
+
151
+ # For both positive and negative
152
+ def path_error_message
153
+ %Q|path "#{path}" does not exists in actual: |
154
+ [
155
+ original_actual.awesome_inspect(indent: -2),
156
+ ].join("\n")
157
+ end
158
+
159
+ def has_valid_path?
160
+ (path.nil? || path.valid?)
161
+ end
162
+
163
+ # For both positive and negative
164
+ def invalid_path_message
165
+ %Q|path "#{path}" is invalid|
166
+ end
167
+
168
+ def comparer_klass
169
+ with_exact_keys? ? Comparers::ExactKeysComparer : Comparers::IncludeKeysComparer
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,12 @@
1
+ require_relative "matchers/be_json_matcher"
2
+ require_relative "matchers/be_json_with_content_matcher"
3
+ require_relative "matchers/be_json_with_sizes_matcher"
4
+
5
+ module RSpec
6
+ module JsonMatchers
7
+ # Mixin Module to be included into RSpec
8
+ # Other files will define the same module and add methods to this module
9
+ module Matchers
10
+ end
11
+ end
12
+ end