delegate_matcher 0.0.3 → 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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.bundle/config +3 -0
  3. data/.gitignore +3 -1
  4. data/.rubocop.yml +8 -0
  5. data/.travis.yml +7 -2
  6. data/Gemfile.lock +90 -37
  7. data/Guardfile +4 -0
  8. data/README.md +106 -98
  9. data/Rakefile +3 -1
  10. data/delegate_matcher.gemspec +17 -8
  11. data/lib/delegate_matcher/delegate.rb +79 -0
  12. data/lib/delegate_matcher/delegate_matcher.rb +41 -266
  13. data/lib/delegate_matcher/delegation.rb +115 -0
  14. data/lib/delegate_matcher/dispatcher.rb +26 -0
  15. data/lib/delegate_matcher/expected.rb +108 -0
  16. data/lib/delegate_matcher/nil_delegate.rb +41 -0
  17. data/lib/delegate_matcher/stub_delegate.rb +26 -0
  18. data/lib/delegate_matcher/version.rb +1 -1
  19. data/lib/delegate_matcher.rb +9 -0
  20. data/spec/lib/active_support_delegation_spec.rb +24 -29
  21. data/spec/lib/aggregate_delegate_matcher_spec.rb +62 -0
  22. data/spec/lib/delegate_spec.rb +15 -0
  23. data/spec/lib/delegate_to_class_variable_spec.rb +85 -0
  24. data/spec/lib/delegate_to_constant_spec.rb +86 -0
  25. data/spec/lib/delegate_to_instance_variable_spec.rb +86 -0
  26. data/spec/lib/delegate_to_method_spec.rb +84 -0
  27. data/spec/lib/delegate_to_object_spec.rb +103 -0
  28. data/spec/lib/forwardable_delegation_spec.rb +14 -13
  29. data/spec/lib/shared/a_simple_delegator.rb +17 -0
  30. data/spec/lib/shared/args.rb +24 -0
  31. data/spec/lib/shared/args_and_a_block.rb +6 -0
  32. data/spec/lib/shared/author.rb +10 -0
  33. data/spec/lib/shared/block.rb +45 -0
  34. data/spec/lib/shared/different_method_name.rb +12 -0
  35. data/spec/lib/shared/different_return_value.rb +19 -0
  36. data/spec/lib/shared/nil_check.rb +52 -0
  37. data/spec/lib/shared/prefix.rb +16 -0
  38. data/spec/spec_helper.rb +6 -2
  39. metadata +85 -7
  40. data/spec/lib/delegate_matcher_spec.rb +0 -467
  41. data/spec/lib/version_spec.rb +0 -7
@@ -0,0 +1,79 @@
1
+ module RSpec
2
+ module Matchers
3
+ module DelegateMatcher
4
+ class Delegate
5
+ RSpec::Mocks::Syntax.enable_expect(self)
6
+
7
+ attr_reader :received, :args, :block
8
+
9
+ def initialize(expected)
10
+ self.expected = expected
11
+ self.received = false
12
+ end
13
+
14
+ # rubocop:disable Metrics/AbcSize
15
+ def receiver
16
+ @receiver ||= case
17
+ when a_class_variable?
18
+ subject.class.class_variable_get(name)
19
+ when an_instance_variable?
20
+ subject.instance_variable_get(name)
21
+ when a_constant?
22
+ subject.class.const_get(name)
23
+ when a_method?
24
+ fail "#{subject.inspect} does not respond to #{name}" unless subject.respond_to?(name, true)
25
+ fail "#{subject.inspect}'s' #{name} method expects parameters" unless [0, -1].include?(subject.method(name).arity)
26
+ subject.send(name)
27
+ else # is an object
28
+ to
29
+ end
30
+ end
31
+
32
+ def argument_description
33
+ args ? "(#{args.map { |a| format('%p', a) }.join(', ')})" : ''
34
+ end
35
+
36
+ def return_value
37
+ self
38
+ end
39
+
40
+ private
41
+
42
+ attr_accessor :expected
43
+ attr_writer :received, :args, :block
44
+
45
+ def subject
46
+ expected.subject
47
+ end
48
+
49
+ def to
50
+ expected.to
51
+ end
52
+
53
+ def name
54
+ to.to_s
55
+ end
56
+
57
+ def a_class_variable?
58
+ a_reference? && name.start_with?('@@')
59
+ end
60
+
61
+ def an_instance_variable?
62
+ a_reference? && name.start_with?('@')
63
+ end
64
+
65
+ def a_constant?
66
+ a_reference? && name =~ /^[A-Z]/
67
+ end
68
+
69
+ def a_method?
70
+ a_reference?
71
+ end
72
+
73
+ def a_reference?
74
+ to.is_a?(String) || to.is_a?(Symbol)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,282 +1,57 @@
1
1
  require 'rspec/matchers'
2
2
 
3
- RSpec::Matchers.define(:delegate) do |method|
4
- match do |delegator|
5
- fail 'need to provide a "to"' unless delegate
3
+ module RSpec
4
+ module Matchers
5
+ define(:delegate) do |method_name|
6
+ match do |subject|
7
+ delegation_ok?(method_name, subject)
8
+ end
6
9
 
7
- @method = method
8
- @delegator = delegator
10
+ description do
11
+ expected.description
12
+ end
9
13
 
10
- allow_nil_ok? && delegate? && arguments_ok? && block_ok?
11
- end
12
-
13
- description do
14
- "delegate #{delegator_description} to #{delegate_description}#{nil_description}#{block_description}"
15
- end
14
+ def failure_message
15
+ delegation.failure_message(false) || super
16
+ end
16
17
 
17
- def failure_message
18
- failure_message_details(false) || super
19
- end
18
+ def failure_message_when_negated
19
+ delegation.failure_message(true) || super
20
+ end
20
21
 
21
- def failure_message_when_negated
22
- failure_message_details(true) || super
23
- end
22
+ chain(:to) { |to| expected.to = to }
23
+ chain(:as) { |as| expected.as = as }
24
+ chain(:allow_nil) { |allow_nil = true| expected.allow_nil = allow_nil }
25
+ chain(:with_prefix) { |prefix = nil| expected.prefix = prefix }
26
+ chain(:with) { |*args| expected.args = args }
27
+ chain(:with_a_block) { expected.block = true }
28
+ chain(:without_a_block) { expected.block = false }
29
+ chain(:without_return) { expected.check_return = false }
24
30
 
25
- chain(:to) { |delegate| @delegate = delegate }
26
- chain(:as) { |delegate_method| @delegate_method = delegate_method }
27
- chain(:allow_nil) { |allow_nil = true| @expected_nil_check = allow_nil }
28
- chain(:with_prefix) { |prefix = nil| @prefix = prefix || delegate.to_s.sub(/@/, '') }
29
- chain(:with) { |*args| @expected_args = args; @args ||= args }
30
- chain(:with_a_block) { @expected_block = true }
31
- chain(:without_a_block) { @expected_block = false }
31
+ alias_method :with_block, :with_a_block
32
+ alias_method :without_block, :without_a_block
32
33
 
33
- alias_method :with_block, :with_a_block
34
- alias_method :without_block, :without_a_block
34
+ private
35
35
 
36
- private
36
+ def delegation
37
+ @delegation ||= DelegateMatcher::Delegation.new(expected)
38
+ end
37
39
 
38
- attr_reader :method, :delegator, :delegate, :prefix, :args
39
- attr_reader :expected_nil_check, :actual_nil_check
40
- attr_reader :expected_args, :actual_args
41
- attr_reader :expected_block, :actual_block
42
- attr_reader :actual_return_value
40
+ def expected
41
+ @expected ||= DelegateMatcher::Expected.new
42
+ end
43
43
 
44
- def delegate?(test_delegate = delegate_double)
45
- case
46
- when delegate_is_a_class_variable?
47
- delegate_to_class_variable(test_delegate)
48
- when delegate_is_an_instance_variable?
49
- delegate_to_instance_variable(test_delegate)
50
- when delegate_is_a_constant?
51
- delegate_to_constant
52
- when delegate_is_a_method?
53
- delegate_to_method(test_delegate)
54
- else
55
- delegate_to_object
56
- end
44
+ def delegation_ok?(method_name, subject)
45
+ fail 'need to provide a "to"' unless expected.to
57
46
 
58
- return_value_ok?
59
- end
60
-
61
- def delegate_is_a_class_variable?
62
- delegate.to_s.start_with?('@@')
63
- end
64
-
65
- def delegate_is_an_instance_variable?
66
- delegate.to_s[0] == '@'
67
- end
68
-
69
- def delegate_is_a_constant?
70
- (delegate.is_a?(String) || delegate.is_a?(Symbol)) && (delegate.to_s =~ /^[A-Z]/)
71
- end
72
-
73
- def delegate_is_a_method?
74
- delegate.is_a?(String) || delegate.is_a?(Symbol)
75
- end
76
-
77
- def delegate_to_class_variable(test_delegate)
78
- actual_delegate = delegator.class.class_variable_get(delegate)
79
- delegator.class.class_variable_set(delegate, test_delegate)
80
- call
81
- ensure
82
- delegator.class.class_variable_set(delegate, actual_delegate)
83
- end
84
-
85
- def delegate_to_instance_variable(test_delegate)
86
- actual_delegate = delegator.instance_variable_get(delegate)
87
- delegator.instance_variable_set(delegate, test_delegate)
88
- call
89
- ensure
90
- delegator.instance_variable_set(delegate, actual_delegate)
91
- end
92
-
93
- def delegate_to_constant
94
- ensure_allow_nil_is_not_specified_for('a constant')
95
- stub_delegation(delegator.class.const_get(delegate))
96
- call
97
- end
47
+ expected.method_name = method_name
48
+ expected.subject = subject
98
49
 
99
- def delegate_to_method(test_delegate)
100
- ensure_delegate_method_is_valid
101
- allow(delegator).to receive(delegate) { test_delegate }
102
- call
103
- end
104
-
105
- def delegate_to_object
106
- ensure_allow_nil_is_not_specified_for('an object')
107
- stub_delegation(delegate)
108
- call
109
- end
110
-
111
- def ensure_allow_nil_is_not_specified_for(target)
112
- fail %(cannot verify "allow_nil" expectations when delegating to #{target}) unless expected_nil_check.nil?
113
- end
114
-
115
- def ensure_delegate_method_is_valid
116
- fail "#{delegator} does not respond to #{delegate}" unless delegator.respond_to?(delegate, true)
117
- fail "#{delegator}'s' #{delegate} method expects parameters" unless [0, -1].include?(delegator.method(delegate).arity)
118
- end
119
-
120
- def delegator_method
121
- @delegator_method || (prefix ? :"#{prefix}_#{method}" : method)
122
- end
123
-
124
- def delegate_method
125
- @delegate_method || method
126
- end
127
-
128
- def call
129
- @actual_return_value = delegator.send(delegator_method, *args, &block)
130
- end
131
-
132
- def block
133
- @block ||= proc {}
134
- end
135
-
136
- def delegate_double
137
- double('delegate').tap { |delegate| stub_delegation(delegate) }
138
- end
139
-
140
- def stub_delegation(delegate)
141
- @delegated = false
142
- allow(delegate).to(receive(delegate_method)) do |*args, &block|
143
- @actual_args = args
144
- @actual_block = block
145
- @delegated = true
146
- expected_return_value
147
- end
148
- end
149
-
150
- def expected_return_value
151
- self
152
- end
153
-
154
- def allow_nil_ok?
155
- return true if expected_nil_check.nil?
156
- return true unless delegate.is_a?(String) || delegate.is_a?(Symbol)
157
-
158
- begin
159
- actual_nil_check = true
160
- delegate?(nil)
161
- @return_value_when_delegate_nil = actual_return_value
162
- rescue NoMethodError
163
- actual_nil_check = false
164
- end
165
-
166
- expected_nil_check == actual_nil_check && @return_value_when_delegate_nil.nil?
167
- end
168
-
169
- def arguments_ok?
170
- expected_args.nil? || actual_args.eql?(expected_args)
171
- end
172
-
173
- def block_ok?
174
- case
175
- when expected_block.nil?
176
- true
177
- when expected_block
178
- actual_block == block
179
- else
180
- actual_block.nil?
181
- end
182
- end
183
-
184
- def return_value_ok?
185
- actual_return_value == expected_return_value
186
- end
187
-
188
- def delegator_description
189
- "#{delegator_method}#{argument_description(args)}"
190
- end
191
-
192
- def delegate_description
193
- case
194
- when !args.eql?(expected_args)
195
- "#{delegate}.#{delegate_method}#{argument_description(expected_args)}"
196
- when delegate_method.eql?(delegator_method)
197
- "#{delegate}"
198
- else
199
- "#{delegate}.#{delegate_method}"
200
- end
201
- end
202
-
203
- def argument_description(args)
204
- args ? "(#{args.map { |a| format('%p', a) }.join(', ')})" : ''
205
- end
206
-
207
- def nil_description
208
- case
209
- when expected_nil_check.nil?
210
- ''
211
- when expected_nil_check
212
- ' with nil allowed'
213
- else
214
- ' with nil not allowed'
215
- end
216
- end
217
-
218
- def block_description
219
- case
220
- when expected_block.nil?
221
- ''
222
- when expected_block
223
- ' with a block'
224
- else
225
- ' without a block'
226
- end
227
- end
228
-
229
- def failure_message_details(negated)
230
- message = [
231
- argument_failure_message(negated),
232
- block_failure_message(negated),
233
- return_value_failure_message(negated),
234
- allow_nil_failure_message(negated)
235
- ].reject(&:empty?).join(' and ')
236
- message.empty? ? nil : message
237
- end
238
-
239
- def argument_failure_message(negated)
240
- case
241
- when expected_args.nil? || negated ^ arguments_ok?
242
- ''
243
- else
244
- "was called with #{argument_description(actual_args)}"
245
- end
246
- end
247
-
248
- def block_failure_message(negated)
249
- case
250
- when expected_block.nil? || (negated ^ block_ok?)
251
- ''
252
- when negated
253
- "a block was #{expected_block ? '' : 'not '}passed"
254
- when expected_block
255
- actual_block.nil? ? 'a block was not passed' : "a different block #{actual_block} was passed"
256
- else
257
- 'a block was passed'
258
- end
259
- end
260
-
261
- def return_value_failure_message(_negated)
262
- case
263
- when !@delegated || return_value_ok?
264
- ''
265
- else
266
- format('a return value of %p was returned instead of the delegate return value', actual_return_value)
267
- end
268
- end
269
-
270
- def allow_nil_failure_message(negated)
271
- case
272
- when expected_nil_check.nil? || negated ^ allow_nil_ok?
273
- ''
274
- when !@return_value_when_delegate_nil.nil?
275
- 'did not return nil'
276
- when negated
277
- "#{delegate} was #{expected_nil_check ? '' : 'not '}allowed to be nil"
278
- else
279
- "#{delegate} was #{expected_nil_check ? 'not ' : ''}allowed to be nil"
50
+ delegation.ok?
51
+ end
280
52
  end
281
53
  end
282
54
  end
55
+
56
+ # TODO: Handle delegation to a class method?
57
+ # TODO: How to handle delegation if delegate_double is called with something else
@@ -0,0 +1,115 @@
1
+ require 'rspec/matchers'
2
+
3
+ module RSpec
4
+ module Matchers
5
+ module DelegateMatcher
6
+ class Delegation
7
+ attr_accessor :expected
8
+ attr_accessor :dispatcher
9
+ attr_accessor :delegate
10
+
11
+ def initialize(expected)
12
+ self.expected = expected
13
+ self.dispatcher = DelegateMatcher::Dispatcher.new(expected)
14
+ self.delegate = StubDelegate.new(expected)
15
+ end
16
+
17
+ def delegation_ok?
18
+ dispatcher.call
19
+ delegate.received
20
+ end
21
+
22
+ def ok?
23
+ allow_nil_ok? && delegation_ok? && arguments_ok? && block_ok? && return_value_ok?
24
+ end
25
+
26
+ def allow_nil_ok?
27
+ return true if expected.allow_nil.nil?
28
+
29
+ begin
30
+ NilDelegate.new(expected) { dispatcher.call }
31
+ allow_nil = true
32
+ rescue NoMethodError
33
+ allow_nil = false
34
+ end
35
+
36
+ allow_nil == expected.allow_nil
37
+ end
38
+
39
+ def arguments_ok?
40
+ expected.as_args.nil? || delegate.args.eql?(expected.as_args)
41
+ end
42
+
43
+ def block_ok?
44
+ case
45
+ when expected.block.nil?
46
+ true
47
+ when expected.block
48
+ delegate.block == dispatcher.block
49
+ else
50
+ delegate.block.nil?
51
+ end
52
+ end
53
+
54
+ def return_value_ok?
55
+ !expected.check_return || dispatcher.return_value == delegate.return_value
56
+ end
57
+
58
+ def failure_message(negated)
59
+ message = [
60
+ argument_failure_message(negated),
61
+ block_failure_message(negated),
62
+ return_value_failure_message(negated),
63
+ allow_nil_failure_message(negated)
64
+ ].reject(&:empty?).join(' and ')
65
+ message.empty? ? nil : message
66
+ end
67
+
68
+ def argument_failure_message(negated)
69
+ case
70
+ when expected.as_args.nil? || negated ^ arguments_ok?
71
+ ''
72
+ else
73
+ "was called with #{delegate.argument_description}"
74
+ end
75
+ end
76
+
77
+ def block_failure_message(negated)
78
+ case
79
+ when expected.block.nil? || (negated ^ block_ok?)
80
+ ''
81
+ when negated
82
+ "a block was #{expected.block ? '' : 'not '}passed"
83
+ when expected.block
84
+ delegate.block.nil? ? 'a block was not passed' : "a different block #{delegate.block} was passed"
85
+ else
86
+ 'a block was passed'
87
+ end
88
+ end
89
+
90
+ def return_value_failure_message(_negated)
91
+ case
92
+ when !delegate.received || return_value_ok?
93
+ ''
94
+ else
95
+ format('a return value of %p was returned instead of the delegate return value', dispatcher.return_value)
96
+ end
97
+ end
98
+
99
+ def allow_nil_failure_message(negated)
100
+ case
101
+ when expected.allow_nil.nil? || negated ^ allow_nil_ok?
102
+ ''
103
+ when negated
104
+ "#{expected.to} was #{expected.allow_nil ? '' : 'not '}allowed to be nil"
105
+ else
106
+ "#{expected.to} was #{expected.allow_nil ? 'not ' : ''}allowed to be nil"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ # TODO: use default prefix with constants - lower case method prefix
115
+ # TODO: How to handle delegation is delegate_double is called with something else
@@ -0,0 +1,26 @@
1
+ module RSpec
2
+ module Matchers
3
+ module DelegateMatcher
4
+ class Dispatcher
5
+ attr_reader :return_value
6
+
7
+ def initialize(expected)
8
+ self.expected = expected
9
+ end
10
+
11
+ def call
12
+ self.return_value = expected.subject.send(expected.delegator_method_name, *expected.args, &block)
13
+ end
14
+
15
+ def block
16
+ @block ||= proc {}
17
+ end
18
+
19
+ private
20
+
21
+ attr_accessor :expected
22
+ attr_writer :return_value
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,108 @@
1
+ module RSpec
2
+ module Matchers
3
+ module DelegateMatcher
4
+ class Expected
5
+ attr_accessor :subject, :to, :method_name, :block, :as, :allow_nil, :check_return
6
+ attr_reader :args
7
+
8
+ def initialize
9
+ self.check_return = true
10
+ end
11
+
12
+ def prefix=(prefix)
13
+ @has_prefix = true
14
+ @prefix = prefix
15
+ end
16
+
17
+ def prefix
18
+ case
19
+ when !@has_prefix
20
+ ''
21
+ when @prefix
22
+ "#{@prefix}_"
23
+ when to.is_a?(String) || to.is_a?(Symbol)
24
+ to.to_s.delete('@').downcase + '_'
25
+ else
26
+ ''
27
+ end
28
+ end
29
+
30
+ def args=(args)
31
+ @args_set ? @as_args = args : @args = args
32
+ @args_set = true
33
+ end
34
+
35
+ def as_args
36
+ @as_args || args
37
+ end
38
+
39
+ def as
40
+ @as || method_name
41
+ end
42
+
43
+ def delegator_method_name
44
+ "#{prefix}#{method_name}"
45
+ end
46
+
47
+ def description
48
+ "delegate #{delegator_description} to #{delegate_description}#{options_description}"
49
+ end
50
+
51
+ private
52
+
53
+ def delegator_description
54
+ "#{delegator_method_name}#{argument_description}"
55
+ end
56
+
57
+ def delegate_description
58
+ case
59
+ when !args.eql?(as_args)
60
+ "#{to}.#{as}#{as_argument_description}"
61
+ when as.to_s.eql?(delegator_method_name)
62
+ "#{to}"
63
+ else
64
+ "#{to}.#{as}"
65
+ end
66
+ end
67
+
68
+ def as_argument_description
69
+ argument_description(as_args)
70
+ end
71
+
72
+ def argument_description(arguments = args)
73
+ arguments ? "(#{arguments.map { |a| format('%p', a) }.join(', ')})" : ''
74
+ end
75
+
76
+ def options_description
77
+ "#{nil_description}#{block_description}#{return_value_description}"
78
+ end
79
+
80
+ def nil_description
81
+ case
82
+ when allow_nil.nil?
83
+ ''
84
+ when allow_nil
85
+ ' with nil allowed'
86
+ else
87
+ ' with nil not allowed'
88
+ end
89
+ end
90
+
91
+ def block_description
92
+ case
93
+ when block.nil?
94
+ ''
95
+ when block
96
+ ' with a block'
97
+ else
98
+ ' without a block'
99
+ end
100
+ end
101
+
102
+ def return_value_description
103
+ check_return ? '' : ' without using delegate return value'
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,41 @@
1
+ module RSpec
2
+ module Matchers
3
+ module DelegateMatcher
4
+ class NilDelegate < Delegate
5
+ def initialize(expected, &block)
6
+ super
7
+ original_receiver = receiver
8
+ self.receiver = nil
9
+ block.call
10
+ ensure
11
+ self.receiver = original_receiver
12
+ end
13
+
14
+ private
15
+
16
+ # rubocop:disable Metrics/AbcSize
17
+ def receiver=(value)
18
+ case
19
+ when a_class_variable?
20
+ subject.class.class_variable_set(name, value)
21
+ when an_instance_variable?
22
+ subject.instance_variable_set(name, value)
23
+ when a_constant?
24
+ silence_warnings { subject.class.const_set(name, value) }
25
+ when a_method?
26
+ allow(subject).to receive(name) { value }
27
+ else # is an object
28
+ fail 'cannot verify "allow_nil" expectations when delegating to an object' if value.nil?
29
+ end
30
+ end
31
+
32
+ def silence_warnings(&block)
33
+ warn_level = $VERBOSE
34
+ $VERBOSE = nil
35
+ block.call
36
+ $VERBOSE = warn_level
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end