rspec_deep_eq 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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