rspec-mocks 3.0.4 → 3.12.6

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 (49) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +512 -2
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +113 -30
  8. data/lib/rspec/mocks/any_instance/chain.rb +5 -3
  9. data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
  10. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +1 -5
  11. data/lib/rspec/mocks/any_instance/expectation_chain.rb +9 -8
  12. data/lib/rspec/mocks/any_instance/message_chains.rb +7 -8
  13. data/lib/rspec/mocks/any_instance/proxy.rb +14 -5
  14. data/lib/rspec/mocks/any_instance/recorder.rb +61 -31
  15. data/lib/rspec/mocks/any_instance/stub_chain.rb +15 -11
  16. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +1 -5
  17. data/lib/rspec/mocks/any_instance.rb +1 -0
  18. data/lib/rspec/mocks/argument_list_matcher.rb +55 -10
  19. data/lib/rspec/mocks/argument_matchers.rb +88 -30
  20. data/lib/rspec/mocks/configuration.rb +61 -13
  21. data/lib/rspec/mocks/error_generator.rb +250 -107
  22. data/lib/rspec/mocks/example_methods.rb +151 -28
  23. data/lib/rspec/mocks/instance_method_stasher.rb +17 -6
  24. data/lib/rspec/mocks/matchers/have_received.rb +50 -20
  25. data/lib/rspec/mocks/matchers/receive.rb +39 -11
  26. data/lib/rspec/mocks/matchers/receive_message_chain.rb +22 -7
  27. data/lib/rspec/mocks/matchers/receive_messages.rb +12 -7
  28. data/lib/rspec/mocks/message_chain.rb +3 -7
  29. data/lib/rspec/mocks/message_expectation.rb +466 -307
  30. data/lib/rspec/mocks/method_double.rb +88 -29
  31. data/lib/rspec/mocks/method_reference.rb +85 -25
  32. data/lib/rspec/mocks/minitest_integration.rb +68 -0
  33. data/lib/rspec/mocks/mutate_const.rb +50 -109
  34. data/lib/rspec/mocks/object_reference.rb +89 -32
  35. data/lib/rspec/mocks/order_group.rb +4 -5
  36. data/lib/rspec/mocks/proxy.rb +156 -60
  37. data/lib/rspec/mocks/space.rb +52 -35
  38. data/lib/rspec/mocks/standalone.rb +1 -1
  39. data/lib/rspec/mocks/syntax.rb +26 -30
  40. data/lib/rspec/mocks/targets.rb +55 -28
  41. data/lib/rspec/mocks/test_double.rb +43 -7
  42. data/lib/rspec/mocks/verifying_double.rb +27 -33
  43. data/lib/rspec/mocks/{verifying_message_expecation.rb → verifying_message_expectation.rb} +11 -16
  44. data/lib/rspec/mocks/verifying_proxy.rb +77 -26
  45. data/lib/rspec/mocks/version.rb +1 -1
  46. data/lib/rspec/mocks.rb +8 -1
  47. data.tar.gz.sig +0 -0
  48. metadata +80 -43
  49. metadata.gz.sig +0 -0
@@ -2,8 +2,11 @@ module RSpec
2
2
  module Mocks
3
3
  # @private
4
4
  class MethodDouble
5
+ # @private TODO: drop in favor of FrozenError in ruby 2.5+
6
+ FROZEN_ERROR_MSG = /can't modify frozen/
7
+
5
8
  # @private
6
- attr_reader :method_name, :object, :expectations, :stubs
9
+ attr_reader :method_name, :object, :expectations, :stubs, :method_stasher
7
10
 
8
11
  # @private
9
12
  def initialize(object, method_name, proxy)
@@ -18,20 +21,31 @@ module RSpec
18
21
  @stubs = []
19
22
  end
20
23
 
21
- def original_method
24
+ def original_implementation_callable
22
25
  # If original method is not present, uses the `method_missing`
23
26
  # handler of the object. This accounts for cases where the user has not
24
27
  # correctly defined `respond_to?`, and also 1.8 which does not provide
25
28
  # method handles for missing methods even if `respond_to?` is correct.
29
+ @original_implementation_callable ||= original_method || method_missing_block
30
+ end
31
+
32
+ alias_method :save_original_implementation_callable!, :original_implementation_callable
33
+
34
+ def original_method
26
35
  @original_method ||=
27
36
  @method_stasher.original_method ||
28
- @proxy.original_method_handle_for(method_name) ||
29
- Proc.new do |*args, &block|
30
- @object.__send__(:method_missing, @method_name, *args, &block)
31
- end
37
+ @proxy.original_method_handle_for(method_name)
32
38
  end
33
39
 
34
- alias_method :save_original_method!, :original_method
40
+ # @private
41
+ def method_missing_block
42
+ block = Proc.new do |*args, &b|
43
+ @object.__send__(:method_missing, @method_name, *args, &b)
44
+ end
45
+ block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
46
+
47
+ block
48
+ end
35
49
 
36
50
  # @private
37
51
  def visibility
@@ -45,7 +59,7 @@ module RSpec
45
59
 
46
60
  # @private
47
61
  def configure_method
48
- @original_visibility = [visibility, method_name]
62
+ @original_visibility = visibility
49
63
  @method_stasher.stash unless @method_is_proxied
50
64
  define_proxy_method
51
65
  end
@@ -54,44 +68,62 @@ module RSpec
54
68
  def define_proxy_method
55
69
  return if @method_is_proxied
56
70
 
57
- save_original_method!
58
- definition_target.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
71
+ save_original_implementation_callable!
72
+ definition_target.class_exec(self, method_name, @original_visibility || visibility) do |method_double, method_name, visibility|
59
73
  define_method(method_name) do |*args, &block|
60
74
  method_double.proxy_method_invoked(self, *args, &block)
61
75
  end
62
- self.__send__ visibility, method_name
76
+ # This can't be `if respond_to?(:ruby2_keywords, true)`,
77
+ # see https://github.com/rspec/rspec-mocks/pull/1385#issuecomment-755340298
78
+ ruby2_keywords(method_name) if Module.private_method_defined?(:ruby2_keywords)
79
+ __send__(visibility, method_name)
63
80
  end
64
81
 
65
82
  @method_is_proxied = true
83
+ rescue RuntimeError, TypeError => e
84
+ # TODO: drop in favor of FrozenError in ruby 2.5+
85
+ # RuntimeError (and FrozenError) for ruby 2.x
86
+ # TypeError for ruby 1.x
87
+ if (defined?(FrozenError) && e.is_a?(FrozenError)) || FROZEN_ERROR_MSG === e.message
88
+ raise ArgumentError, "Cannot proxy frozen objects, rspec-mocks relies on proxies for method stubbing and expectations."
89
+ end
90
+ raise
66
91
  end
67
92
 
68
93
  # The implementation of the proxied method. Subclasses may override this
69
94
  # method to perform additional operations.
70
95
  #
71
96
  # @private
72
- def proxy_method_invoked(obj, *args, &block)
97
+ def proxy_method_invoked(_obj, *args, &block)
73
98
  @proxy.message_received method_name, *args, &block
74
99
  end
100
+ ruby2_keywords :proxy_method_invoked if respond_to?(:ruby2_keywords, true)
75
101
 
76
102
  # @private
77
103
  def restore_original_method
78
- return show_frozen_warning if object_singleton_class.frozen?
79
104
  return unless @method_is_proxied
80
105
 
81
- definition_target.__send__(:remove_method, @method_name)
82
-
83
- if @method_stasher.method_is_stashed?
84
- @method_stasher.restore
85
- end
106
+ remove_method_from_definition_target
107
+ @method_stasher.restore if @method_stasher.method_is_stashed?
86
108
  restore_original_visibility
87
109
 
88
110
  @method_is_proxied = false
111
+ rescue RuntimeError, TypeError => e
112
+ # TODO: drop in favor of FrozenError in ruby 2.5+
113
+ # RuntimeError (and FrozenError) for ruby 2.x
114
+ # TypeError for ruby 1.x
115
+ if (defined?(FrozenError) && e.is_a?(FrozenError)) || FROZEN_ERROR_MSG === e.message
116
+ return show_frozen_warning
117
+ end
118
+ raise
89
119
  end
90
120
 
91
121
  # @private
92
122
  def show_frozen_warning
93
123
  RSpec.warn_with(
94
- "WARNING: rspec-mocks was unable to restore the original `#{@method_name}` method on #{@object.inspect} because it has been frozen. If you reuse this object, `#{@method_name}` will continue to respond with its stub implementation.",
124
+ "WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \
125
+ "method on #{@object.inspect} because it has been frozen. If you reuse this " \
126
+ "object, `#{@method_name}` will continue to respond with its stub implementation.",
95
127
  :call_site => nil,
96
128
  :use_spec_location_as_call_site => true
97
129
  )
@@ -102,12 +134,12 @@ module RSpec
102
134
  return unless @original_visibility &&
103
135
  MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
104
136
 
105
- object_singleton_class.__send__(*@original_visibility)
137
+ object_singleton_class.__send__(@original_visibility, method_name)
106
138
  end
107
139
 
108
140
  # @private
109
141
  def verify
110
- expectations.each {|e| e.verify_messages_received}
142
+ expectations.each { |e| e.verify_messages_received }
111
143
  end
112
144
 
113
145
  # @private
@@ -134,7 +166,7 @@ module RSpec
134
166
  def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
135
167
  configure_method
136
168
  expectation = message_expectation_class.new(error_generator, expectation_ordering,
137
- expected_from, self, :expectation, opts, &implementation)
169
+ expected_from, self, :expectation, opts, &implementation)
138
170
  expectations << expectation
139
171
  expectation
140
172
  end
@@ -149,7 +181,7 @@ module RSpec
149
181
  def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
150
182
  configure_method
151
183
  stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
152
- self, :stub, opts, &implementation)
184
+ self, :stub, opts, &implementation)
153
185
  stubs.unshift stub
154
186
  stub
155
187
  end
@@ -172,7 +204,7 @@ module RSpec
172
204
  end
173
205
 
174
206
  # @private
175
- def setup_simple_method_double(method_name, response, collection, error_generator = nil, backtrace_line = nil)
207
+ def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil)
176
208
  define_proxy_method
177
209
 
178
210
  me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
@@ -199,15 +231,13 @@ module RSpec
199
231
 
200
232
  # @private
201
233
  def raise_method_not_stubbed_error
202
- raise MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
234
+ RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name)
203
235
  end
204
236
 
205
- private
206
-
207
237
  # In Ruby 2.0.0 and above prepend will alter the method lookup chain.
208
238
  # We use an object's singleton class to define method doubles upon,
209
- # however if the object has had it's singleton class (as opposed to
210
- # it's actual class) prepended too then the the method lookup chain
239
+ # however if the object has had its singleton class (as opposed to
240
+ # its actual class) prepended too then the the method lookup chain
211
241
  # will look in the prepended module first, **before** the singleton
212
242
  # class.
213
243
  #
@@ -216,6 +246,9 @@ module RSpec
216
246
  # of our own.
217
247
  #
218
248
  if Support::RubyFeatures.module_prepends_supported?
249
+
250
+ private
251
+
219
252
  # We subclass `Module` in order to be able to easily detect our prepended module.
220
253
  RSpecPrependedModule = Class.new(Module)
221
254
 
@@ -247,11 +280,37 @@ module RSpec
247
280
 
248
281
  else
249
282
 
283
+ private
284
+
250
285
  def definition_target
251
286
  object_singleton_class
252
287
  end
253
288
 
254
289
  end
290
+
291
+ private
292
+
293
+ def remove_method_from_definition_target
294
+ definition_target.__send__(:remove_method, @method_name)
295
+ rescue NameError
296
+ # This can happen when the method has been monkeyed with by
297
+ # something outside RSpec. This happens, for example, when
298
+ # `file.write` has been stubbed, and then `file.reopen(other_io)`
299
+ # is later called, as `File#reopen` appears to redefine `write`.
300
+ #
301
+ # Note: we could avoid rescuing this by checking
302
+ # `definition_target.instance_method(@method_name).owner == definition_target`,
303
+ # saving us from the cost of the expensive exception, but this error is
304
+ # extremely rare (it was discovered on 2014-12-30, only happens on
305
+ # RUBY_VERSION < 2.0 and our spec suite only hits this condition once),
306
+ # so we'd rather avoid the cost of that check for every method double,
307
+ # and risk the rare situation where this exception will get raised.
308
+ RSpec.warn_with(
309
+ "WARNING: RSpec could not fully restore #{@object.inspect}." \
310
+ "#{@method_name}, possibly because the method has been redefined " \
311
+ "by something outside of RSpec."
312
+ )
313
+ end
255
314
  end
256
315
  end
257
316
  end
@@ -1,3 +1,5 @@
1
+ RSpec::Support.require_rspec_support 'comparable_version'
2
+
1
3
  module RSpec
2
4
  module Mocks
3
5
  # Represents a method on an object that may or may not be defined.
@@ -6,6 +8,10 @@ module RSpec
6
8
  #
7
9
  # @private
8
10
  class MethodReference
11
+ def self.for(object_reference, method_name)
12
+ new(object_reference, method_name)
13
+ end
14
+
9
15
  def initialize(object_reference, method_name)
10
16
  @object_reference = object_reference
11
17
  @method_name = method_name
@@ -27,7 +33,7 @@ module RSpec
27
33
  # cases when we don't know if a method is implemented and
28
34
  # both `implemented?` and `unimplemented?` will return false.
29
35
  def unimplemented?
30
- @object_reference.when_loaded do |m|
36
+ @object_reference.when_loaded do |_m|
31
37
  return !implemented?
32
38
  end
33
39
 
@@ -44,9 +50,8 @@ module RSpec
44
50
  end
45
51
 
46
52
  def with_signature
47
- if original = original_method
48
- yield Support::MethodSignature.new(original)
49
- end
53
+ return unless (original = original_method)
54
+ yield Support::MethodSignature.new(original)
50
55
  end
51
56
 
52
57
  def visibility
@@ -59,14 +64,6 @@ module RSpec
59
64
  :public
60
65
  end
61
66
 
62
- private
63
-
64
- def original_method
65
- @object_reference.when_loaded do |m|
66
- self.defined? && find_method(m)
67
- end
68
- end
69
-
70
67
  def self.instance_method_visibility_for(klass, method_name)
71
68
  if klass.public_method_defined?(method_name)
72
69
  :public
@@ -82,23 +79,40 @@ module RSpec
82
79
  end
83
80
 
84
81
  def self.method_visibility_for(object, method_name)
85
- instance_method_visibility_for(class << object; self; end, method_name).tap do |vis|
86
- # If the method is not defined on the class, `instance_method_visibility_for`
87
- # returns `nil`. However, it may be handled dynamically by `method_missing`,
88
- # so here we check `respond_to` (passing false to not check private methods).
89
- #
90
- # This only considers the public case, but I don't think it's possible to
91
- # write `method_missing` in such a way that it handles a dynamic message
92
- # with private or protected visibility. Ruby doesn't provide you with
93
- # the caller info.
94
- return :public if vis.nil? && object.respond_to?(method_name, false)
82
+ vis = instance_method_visibility_for(class << object; self; end, method_name)
83
+
84
+ # If the method is not defined on the class, `instance_method_visibility_for`
85
+ # returns `nil`. However, it may be handled dynamically by `method_missing`,
86
+ # so here we check `respond_to` (passing false to not check private methods).
87
+ #
88
+ # This only considers the public case, but I don't think it's possible to
89
+ # write `method_missing` in such a way that it handles a dynamic message
90
+ # with private or protected visibility. Ruby doesn't provide you with
91
+ # the caller info.
92
+ return vis unless vis.nil?
93
+
94
+ proxy = RSpec::Mocks.space.proxy_for(object)
95
+ respond_to = proxy.method_double_if_exists_for_message(:respond_to?)
96
+
97
+ visible = respond_to && respond_to.original_method.call(method_name) ||
98
+ object.respond_to?(method_name)
99
+
100
+ return :public if visible
101
+ end
102
+
103
+ private
104
+
105
+ def original_method
106
+ @object_reference.when_loaded do |m|
107
+ self.defined? && find_method(m)
95
108
  end
96
109
  end
97
110
  end
98
111
 
99
112
  # @private
100
113
  class InstanceMethodReference < MethodReference
101
- private
114
+ private
115
+
102
116
  def method_implemented?(mod)
103
117
  MethodReference.method_defined_at_any_visibility?(mod, @method_name)
104
118
  end
@@ -116,7 +130,7 @@ module RSpec
116
130
  #
117
131
  # This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
118
132
  # https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
119
- if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
133
+ if RUBY_PLATFORM == 'java' && RSpec::Support::ComparableVersion.new(JRUBY_VERSION) < '1.7.5'
120
134
  def find_method(mod)
121
135
  mod.dup.instance_method(@method_name)
122
136
  end
@@ -133,7 +147,16 @@ module RSpec
133
147
 
134
148
  # @private
135
149
  class ObjectMethodReference < MethodReference
136
- private
150
+ def self.for(object_reference, method_name)
151
+ if ClassNewMethodReference.applies_to?(method_name) { object_reference.when_loaded { |o| o } }
152
+ ClassNewMethodReference.new(object_reference, method_name)
153
+ else
154
+ super
155
+ end
156
+ end
157
+
158
+ private
159
+
137
160
  def method_implemented?(object)
138
161
  object.respond_to?(@method_name, true)
139
162
  end
@@ -150,5 +173,42 @@ module RSpec
150
173
  MethodReference.method_visibility_for(object, @method_name)
151
174
  end
152
175
  end
176
+
177
+ # When a class's `.new` method is stubbed, we want to use the method
178
+ # signature from `#initialize` because `.new`'s signature is a generic
179
+ # `def new(*args)` and it simply delegates to `#initialize` and forwards
180
+ # all args...so the method with the actually used signature is `#initialize`.
181
+ #
182
+ # This method reference implementation handles that specific case.
183
+ # @private
184
+ class ClassNewMethodReference < ObjectMethodReference
185
+ def self.applies_to?(method_name)
186
+ return false unless method_name == :new
187
+ klass = yield
188
+ return false unless ::Class === klass && klass.respond_to?(:new, true)
189
+
190
+ # We only want to apply our special logic to normal `new` methods.
191
+ # Methods that the user has monkeyed with should be left as-is.
192
+ uses_class_new?(klass)
193
+ end
194
+
195
+ if RUBY_VERSION.to_i >= 3
196
+ CLASS_NEW = ::Class.instance_method(:new)
197
+
198
+ def self.uses_class_new?(klass)
199
+ ::RSpec::Support.method_handle_for(klass, :new) == CLASS_NEW.bind(klass)
200
+ end
201
+ else # Ruby 2's Method#== is too strict
202
+ def self.uses_class_new?(klass)
203
+ ::RSpec::Support.method_handle_for(klass, :new).owner == ::Class
204
+ end
205
+ end
206
+
207
+ def with_signature
208
+ @object_reference.when_loaded do |klass|
209
+ yield Support::MethodSignature.new(klass.instance_method(:initialize))
210
+ end
211
+ end
212
+ end
153
213
  end
154
214
  end
@@ -0,0 +1,68 @@
1
+ require 'rspec/mocks'
2
+
3
+ module RSpec
4
+ module Mocks
5
+ # @private
6
+ module MinitestIntegration
7
+ include ::RSpec::Mocks::ExampleMethods
8
+
9
+ def before_setup
10
+ ::RSpec::Mocks.setup
11
+ super
12
+ end
13
+
14
+ def after_teardown
15
+ super
16
+
17
+ # Only verify if there's not already an error. Otherwise
18
+ # we risk getting the same failure twice, since negative
19
+ # expectation violations raise both when the message is
20
+ # unexpectedly received, and also during `verify` (in case
21
+ # the first failure was caught by user code via a
22
+ # `rescue Exception`).
23
+ ::RSpec::Mocks.verify unless failures.any?
24
+ ensure
25
+ ::RSpec::Mocks.teardown
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ Minitest::Test.send(:include, RSpec::Mocks::MinitestIntegration)
32
+
33
+ if defined?(::Minitest::Expectation)
34
+ if defined?(::RSpec::Expectations) && ::Minitest::Expectation.method_defined?(:to)
35
+ # rspec/expectations/minitest_integration has already been loaded and
36
+ # has defined `to`/`not_to`/`to_not` on `Minitest::Expectation` so we do
37
+ # not want to here (or else we would interfere with rspec-expectations' definition).
38
+ else
39
+ # ...otherwise, define those methods now. If `rspec/expectations/minitest_integration`
40
+ # is loaded after this file, it'll override the definition here.
41
+ Minitest::Expectation.class_eval do
42
+ include RSpec::Mocks::ExpectationTargetMethods
43
+
44
+ def to(*args)
45
+ ctx.assertions += 1
46
+ super
47
+ end
48
+
49
+ def not_to(*args)
50
+ ctx.assertions += 1
51
+ super
52
+ end
53
+
54
+ def to_not(*args)
55
+ ctx.assertions += 1
56
+ super
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ module RSpec
63
+ module Mocks
64
+ remove_const :MockExpectationError
65
+ # Raised when a message expectation is not satisfied.
66
+ MockExpectationError = ::Minitest::Assertion
67
+ end
68
+ end