delegate_matcher 0.0.3 → 0.1

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