rspec-json_matchers 0.1.0.alpha.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.
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