rspec_deep_eq 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b8e7e2b845d478ad4db3d4633d7edd0ae38c2d626b0307c4c588a83d655bdfaf
4
+ data.tar.gz: 2105b476d66f1cea36f96690791a21d39be20a44fe60977f673b9c99bfd4f60f
5
+ SHA512:
6
+ metadata.gz: d685dc53ee147d2cf72d7174684342dc2b9f1e545336c78fa01cafe13d476228daeded1f75608eaf02789bc34cb808cc30f3d351bdb74704f696200b464658ea
7
+ data.tar.gz: 32c8f8b3551af6992b9234b8aa5ca43ca187f99dab6ce503587c47d2a9e090b72d7716cf35fabc7c8f5690025bdfb6e9c5bb974453af7fe12c5c7df2cfb06420
@@ -0,0 +1,70 @@
1
+ # RSpec Deep Eq Matches
2
+
3
+ This gem adds an RSpec `deep_eq` matcher that recursively compares contents of `Hash` and `Array` data.
4
+
5
+ ## Installation
6
+ Add gem to your `Gemfile`:
7
+ ```ruby
8
+ gem 'rspec_deep_eq'
9
+ ```
10
+
11
+ And run
12
+ ```
13
+ bundle install
14
+ ```
15
+
16
+ Or install it manually:
17
+ ```bash
18
+ gem install rspec_deep_eq
19
+ ```
20
+
21
+ ## Getting started
22
+ In `rails_helper` file, configure `deep_eq` matcher for RSpec:
23
+
24
+ ```ruby
25
+ # rails_helper.rb
26
+
27
+ require 'deep_eq'
28
+
29
+ RSpec.configure do |config|
30
+ config.include DeepEq
31
+ end
32
+
33
+ ```
34
+
35
+ ## Usage
36
+ You can compare everything with `deep_eq` matcher, but it's created to compare complex structures like nested hashes and arrays.
37
+
38
+ ```ruby
39
+ describe 'Deep Equal' do
40
+ it 'compares' do
41
+ data = prepare_data
42
+
43
+ expect(data).to deep_eq(
44
+ id: 1,
45
+ items: [
46
+ id: 3,
47
+ images: [
48
+ {
49
+ url: 'https://example.com/bass_guitar.png'
50
+ },
51
+ {
52
+ url: 'https://example.com/naked_lady.png'
53
+ }
54
+ ]
55
+ ],
56
+ author: {
57
+ name: 'John Doe'
58
+ }
59
+ )
60
+ end
61
+ end
62
+ ```
63
+
64
+ ## Motivation
65
+ Comparing complex data structures can be easily done using built-in RSpec matchers like `eq`, `match_array` or `hash_including`. But when something goes wrong, it displays hard-to-read error output. This matcher is created to make fixing such tests easier.
66
+
67
+ Read more [here](MOTIVATION.md).
68
+
69
+ ## License
70
+ Published under MIT license.
@@ -0,0 +1,7 @@
1
+ require 'deep_eq/matcher'
2
+
3
+ module DeepEq
4
+ def deep_eq(expected)
5
+ Matcher.new(expected)
6
+ end
7
+ end
@@ -0,0 +1,135 @@
1
+ module DeepEq
2
+ class DeepEqualChecker
3
+ def initialize(current:, expected:, location:, matcher_object:)
4
+ @current = current
5
+ @expected = expected
6
+ @location = location
7
+ @matcher_object = matcher_object
8
+ end
9
+
10
+ def perform
11
+ case expected
12
+ when Array
13
+ return unless current_ensure_array
14
+ return unless current_ensure_count
15
+
16
+ expected.count.times do |index|
17
+ DeepEqualChecker.new(
18
+ current: current[index],
19
+ expected: expected[index],
20
+ location: location + "[#{index}]",
21
+ matcher_object: matcher_object
22
+ ).perform
23
+ end
24
+ when Hash
25
+ return unless current_ensure_hash
26
+ return unless current_ensure_keys
27
+
28
+ expected.keys.each do |key|
29
+ d = DeepEqualChecker.new(
30
+ current: current[key],
31
+ expected: expected[key],
32
+ location: location + "[#{key.inspect}]",
33
+ matcher_object: matcher_object
34
+ ).perform
35
+ end
36
+ else
37
+ compare_values
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def compare_values
44
+ return if current == expected
45
+
46
+ add_error "#{location} is invalid (expected #{expected.inspect}, got #{current.inspect})"
47
+ end
48
+
49
+ def add_error(error)
50
+ matcher_object.add_error error
51
+ false
52
+ end
53
+
54
+ def current_ensure_hash
55
+ return true if current.is_a? Hash
56
+
57
+ add_type_mismatch_error('Hash')
58
+ end
59
+
60
+ def current_ensure_array
61
+ return true if current.is_a? Array
62
+
63
+ add_type_mismatch_error('Array')
64
+ end
65
+
66
+ def current_ensure_count
67
+ return true if current.count === expected.count
68
+
69
+ add_error "#{location} is expected to have length #{expected.count}, but has #{current.count}"
70
+ end
71
+
72
+ def current_ensure_keys
73
+ bad_keys = current.keys.reject { |key| expected.keys.include? key }
74
+
75
+ return true if bad_keys.empty?
76
+
77
+ add_error "#{location} is expected to not contain keys: #{bad_keys.map(&:inspect).join(', ')}"
78
+ end
79
+
80
+ def add_type_mismatch_error(expected_class)
81
+ add_error "#{location} is expected to be #{expected_class}, but #{current.class} found"
82
+ end
83
+
84
+ attr_reader :current, :expected, :location, :matcher_object
85
+ end
86
+ end
87
+
88
+
89
+
90
+
91
+ # module DeepEql
92
+ # module Matchers
93
+ #
94
+ # def deep_eql(expected)
95
+ # DeepEql.new(expected)
96
+ # end
97
+ #
98
+ # class DeepEql
99
+ #
100
+ # def initialize(expectation)
101
+ # @expectation = expectation
102
+ # end
103
+ #
104
+ # def matches?(target)
105
+ # result = true
106
+ # @target = target
107
+ # case @expectation
108
+ # when Hash
109
+ # result &&= @target.is_a?(Hash) && @target.keys.count == @expectation.keys.count
110
+ # @expectation.keys.each do |key|
111
+ # result &&= @target.has_key?(key) &&
112
+ # DeepEql.new(@expectation[key]).matches?(@target[key])
113
+ # end
114
+ # when Array
115
+ # result &&= @target.is_a?(Array) && @target.count == @expectation.count
116
+ # @expectation.each_index do |index|
117
+ # result &&= DeepEql.new(@expectation[index]).matches?(@target[index])
118
+ # end
119
+ # else
120
+ # result &&= @target == @expectation
121
+ # end
122
+ # result
123
+ # end
124
+ #
125
+ # def failure_message_for_should
126
+ # "expected #{@target.inspect} to be deep_eql with #{@expectation.inspect}"
127
+ # end
128
+ #
129
+ # def failure_message_for_should_not
130
+ # "expected #{@target.inspect} not to be in deep_eql with #{@expectation.inspect}"
131
+ # end
132
+ # end
133
+ #
134
+ # end
135
+ # end
@@ -0,0 +1,58 @@
1
+ require_relative './deep_equal_checker'
2
+
3
+ module DeepEq
4
+ class Matcher
5
+ def initialize(expected)
6
+ @expected = expected
7
+ @errors = []
8
+ end
9
+
10
+ def matches?(current)
11
+ @current = current
12
+
13
+ perform_deep_equal_check
14
+
15
+ errors.empty?
16
+ end
17
+
18
+ def failure_message
19
+ message = [
20
+ 'Expected',
21
+ current.inspect,
22
+ "\nto be deep equal to",
23
+ expected.inspect,
24
+ '',
25
+ "\nFollowing errors found:"
26
+ ]
27
+
28
+ message.concat(errors).push("\r").join("\n")
29
+ end
30
+
31
+ def failure_message_when_negated
32
+ message = [
33
+ 'Expected',
34
+ current.inspect,
35
+ "\nnot to be deep equal to",
36
+ expected.inspect,
37
+ "\nBut equal values found."
38
+ ].join("\n") + "\n"
39
+ end
40
+
41
+ def add_error(error)
42
+ errors.push error
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :errors, :current, :expected
48
+
49
+ def perform_deep_equal_check
50
+ DeepEqualChecker.new(
51
+ current: current,
52
+ expected: expected,
53
+ location: 'value',
54
+ matcher_object: self
55
+ ).perform
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module DeepEq
2
+ VERSION = Gem::Version.new('0.0.1')
3
+ end
@@ -0,0 +1,265 @@
1
+ require 'spec_helper'
2
+
3
+ describe DeepEq::Matcher do
4
+ describe 'deep_eq' do
5
+ let!(:matcher_object) { DeepEq::Matcher.new(expected) }
6
+ let(:errors) { [] }
7
+
8
+ before do
9
+ allow(DeepEq::Matcher).to receive(:new).and_return(matcher_object)
10
+ allow(matcher_object).to receive(:add_error).and_call_original
11
+ end
12
+
13
+ after do
14
+ expect(matcher_object).to have_received(:add_error).exactly(errors.count).times
15
+
16
+ errors.each do |error|
17
+ expect(matcher_object).to have_received(:add_error).with(error)
18
+ end
19
+ end
20
+
21
+ context 'equality check' do
22
+ after do
23
+ expect(current).to deep_eq expected
24
+ end
25
+
26
+ context 'when comparing integers' do
27
+ let(:current) { 2 }
28
+ let(:expected) { 2.0 }
29
+
30
+ it 'passes' do; end
31
+ end
32
+
33
+ context 'when comparing strings' do
34
+ let(:current) { 'abc' }
35
+ let(:expected) { 'abc' }
36
+
37
+ it 'passes' do; end
38
+ end
39
+
40
+ context 'when comparing symbols' do
41
+ let(:current) { :def }
42
+ let(:expected) { :"def" }
43
+
44
+ it 'passes' do; end
45
+ end
46
+
47
+ context 'when comparing objects' do
48
+ let(:current) { FakeRecord.new(value: 12) }
49
+ let(:expected) { FakeRecord.new(value: 12.0) }
50
+
51
+ it 'passes' do; end
52
+ end
53
+
54
+ context 'when comparing arrays' do
55
+ let(:current) { [1, 4, 2] }
56
+ let(:expected) { [1, 4, 2] }
57
+
58
+ it 'passes' do; end
59
+
60
+ context 'with arrays' do
61
+ let(:current) { [1, 2, [3, 4, 5], 6, [7]] }
62
+ let(:expected) { [1, 2, [3, 4, 5], 6, [7]] }
63
+
64
+ it 'passes' do; end
65
+ end
66
+
67
+ context 'with hashes' do
68
+ let(:current) { [1, 2, { foo: 'bar', baz: 1 }] }
69
+ let(:expected) { [1, 2, { baz: 1, foo: 'bar' }] }
70
+
71
+ it 'passes' do; end
72
+ end
73
+ end
74
+
75
+ context 'when comparing hashes' do
76
+ let(:current) { { one: 1, two: 2, three: 3 } }
77
+ let(:expected) { { one: 1, three: 3, two: 2 } }
78
+
79
+ it 'passes' do; end
80
+
81
+ context 'with arrays' do
82
+ let(:current) { { one: 1, two: [2, 3, 4]} }
83
+ let(:expected) { { two: [2, 3, 4], one: 1 } }
84
+
85
+ it 'passes' do; end
86
+ end
87
+
88
+ context 'with hashes' do
89
+ let(:current) { { one: 1, two: { full: 2.0, half: 2.5 } } }
90
+ let(:expected) { { two: { full: 2, half: 2.5 }, one: 1 } }
91
+
92
+ it 'passes' do; end
93
+ end
94
+ end
95
+ end
96
+
97
+ context 'inequality check' do
98
+ after do
99
+ expect(current).to_not deep_eq expected
100
+ end
101
+
102
+ context 'when comparing integers' do
103
+ let(:current) { 2 }
104
+ let(:expected) { 3.0 }
105
+ let(:errors) {
106
+ ['value is invalid (expected 3.0, got 2)']
107
+ }
108
+
109
+ it 'passes with errors' do; end
110
+ end
111
+
112
+ context 'when comparing strings' do
113
+ let(:current) { 'abc' }
114
+ let(:expected) { 'abx' }
115
+ let(:errors) {
116
+ ['value is invalid (expected "abx", got "abc")']
117
+ }
118
+
119
+ it 'passes with errors' do; end
120
+ end
121
+
122
+ context 'when comparing symbols' do
123
+ let(:current) { :def }
124
+ let(:expected) { :"defo" }
125
+ let(:errors) {
126
+ ['value is invalid (expected :defo, got :def)']
127
+ }
128
+
129
+ it 'passes with errors' do; end
130
+ end
131
+
132
+ context 'when comparing objects' do
133
+ let(:current) { FakeRecord.new(value: 12) }
134
+ let(:expected) { FakeRecord.new(value: 13) }
135
+ let(:errors) {
136
+ ['value is invalid (expected #<FakeRecord @value=13>, got #<FakeRecord @value=12>)']
137
+ }
138
+
139
+ it 'passes with errors' do; end
140
+ end
141
+
142
+ context 'when comparing arrays' do
143
+ context 'of integers' do
144
+ let(:current) { [1, 4, 2] }
145
+ let(:expected) { [1, 3, 2] }
146
+ let(:errors) {
147
+ ['value[1] is invalid (expected 3, got 4)']
148
+ }
149
+
150
+ it 'passes with errors' do; end
151
+ end
152
+
153
+ context 'with arrays' do
154
+ let(:current) { [1, 2, [3, 5, 7], 6, [7]] }
155
+ let(:expected) { [1, 2, [3, 4, 7], 6, [7]] }
156
+ let(:errors) {
157
+ ['value[2][1] is invalid (expected 4, got 5)']
158
+ }
159
+
160
+ it 'passes with errors' do; end
161
+ end
162
+
163
+ context 'with hashes' do
164
+ let(:current) { [1, 2, { foo: 'bar', baz: 1 }] }
165
+ let(:expected) { [1, 2, { foo: 'baz', baz: 1 }] }
166
+ let(:errors) {
167
+ ['value[2][:foo] is invalid (expected "baz", got "bar")']
168
+ }
169
+
170
+ it 'passes with errors' do; end
171
+ end
172
+ end
173
+
174
+ context 'when comparing hashes' do
175
+ context 'with integers' do
176
+ let(:current) { { one: 1, two: 2, three: 3 } }
177
+ let(:expected) { { one: 1, three: 30, two: 2 } }
178
+ let(:errors) {
179
+ ['value[:three] is invalid (expected 30, got 3)']
180
+ }
181
+
182
+ it 'passes with errors' do; end
183
+ end
184
+
185
+
186
+ context 'with arrays' do
187
+ let(:current) { { one: 1, two: [2, 3, 4] } }
188
+ let(:expected) { { two: [2, 3, 4], one: '1' } }
189
+ let(:errors) {
190
+ ['value[:one] is invalid (expected "1", got 1)']
191
+ }
192
+
193
+ it 'passes with errors' do; end
194
+ end
195
+
196
+ context 'with hashes' do
197
+ let(:current) { { one: 1, two: { full: 2.0, half: 2.5 } } }
198
+ let(:expected) { { two: { full: 2, half: 'e' }, one: 1 } }
199
+ let(:errors) {
200
+ ['value[:two][:half] is invalid (expected "e", got 2.5)']
201
+ }
202
+
203
+ it 'passes with errors' do; end
204
+ end
205
+ end
206
+
207
+ context 'when comparing hash with integer' do
208
+ let(:current) { {} }
209
+ let(:expected) { [] }
210
+ let(:errors) {
211
+ ['value is expected to be Array, but Hash found']
212
+ }
213
+
214
+ it 'passes with errors' do; end
215
+ end
216
+
217
+ context 'when comparing hash with incorrect key' do
218
+ let(:current) { { foo: { bar: { baz: 10 } } } }
219
+ let(:expected) { { foo: { bar: { 'baz' => 10 } } } }
220
+ let(:errors) {
221
+ ['value[:foo][:bar] is expected to not contain keys: :baz']
222
+ }
223
+
224
+ it 'passes with errors' do; end
225
+ end
226
+
227
+ context 'when comparing arrays of different length' do
228
+ context 'when expected is longer' do
229
+ let(:current) { [nil, [1, 2]] }
230
+ let(:expected) { [nil, [1, 2, 3, 4]] }
231
+ let(:errors) {
232
+ ['value[1] is expected to have length 4, but has 2']
233
+ }
234
+
235
+ it 'passes with errors' do; end
236
+ end
237
+
238
+ context 'when expected is shorter' do
239
+ let(:current) { [[1, 2, 3, 4]] }
240
+ let(:expected) { [[1, 2, 3]] }
241
+ let(:errors) {
242
+ ['value[0] is expected to have length 3, but has 4']
243
+ }
244
+
245
+ it 'passes with errors' do; end
246
+ end
247
+ end
248
+
249
+ context 'when comparing objects with multiple errors' do
250
+ let(:current) { [4, {}, [{ id: 10 }, { id: 15, name: 'Joe' }], { foo: { bar: :baz } }] }
251
+ let(:expected) { [3, [], [{ id: 10 }, { id: 150 }], { foo: { bar: :not_baz } }] }
252
+ let(:errors) {
253
+ [
254
+ 'value[0] is invalid (expected 3, got 4)',
255
+ 'value[1] is expected to be Array, but Hash found',
256
+ 'value[2][1] is expected to not contain keys: :name',
257
+ 'value[3][:foo][:bar] is invalid (expected :not_baz, got :baz)'
258
+ ]
259
+ }
260
+
261
+ it 'passes with errors' do; end
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,17 @@
1
+ class FakeRecord
2
+ def initialize(value:)
3
+ @value = value
4
+ end
5
+
6
+ def ==(another_record)
7
+ if another_record.is_a?(FakeRecord)
8
+ value == another_record.value
9
+ end
10
+ end
11
+
12
+ def inspect
13
+ "#<FakeRecord @value=#{value}>"
14
+ end
15
+
16
+ attr_reader :value
17
+ end
@@ -0,0 +1,5 @@
1
+ class DeepEq::Matcher
2
+ def inspect
3
+ "instance(DeepEq::Matcher)"
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ require 'rspec'
2
+ require 'deep_eq'
3
+ require 'pry-rails'
4
+ require 'deep_eq'
5
+
6
+ require_relative 'mocks/fake_record'
7
+ require_relative 'mocks/matcher'
8
+
9
+ module RSpec::Matchers
10
+ include DeepEq
11
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec_deep_eq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - polakowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: RSpec deep_eq matcher
56
+ email:
57
+ - marek.polakowski@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - README.md
63
+ - lib/deep_eq.rb
64
+ - lib/deep_eq/deep_equal_checker.rb
65
+ - lib/deep_eq/matcher.rb
66
+ - lib/deep_eq/version.rb
67
+ - spec/lib/deep_eq/matcher_spec.rb
68
+ - spec/mocks/fake_record.rb
69
+ - spec/mocks/matcher.rb
70
+ - spec/spec_helper.rb
71
+ homepage: http://github.com/polakowski/rspec_deep_eq
72
+ licenses: []
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.0.3
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: RSpec deep_eq matcher
93
+ test_files:
94
+ - spec/mocks/fake_record.rb
95
+ - spec/mocks/matcher.rb
96
+ - spec/spec_helper.rb
97
+ - spec/lib/deep_eq/matcher_spec.rb