rspec-mocks 3.1.3 → 3.2.0

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.
@@ -78,8 +78,7 @@ module RSpec
78
78
  return show_frozen_warning if object_singleton_class.frozen?
79
79
  return unless @method_is_proxied
80
80
 
81
- definition_target.__send__(:remove_method, @method_name)
82
-
81
+ remove_method_from_definition_target
83
82
  @method_stasher.restore if @method_stasher.method_is_stashed?
84
83
  restore_original_visibility
85
84
 
@@ -255,6 +254,30 @@ module RSpec
255
254
  end
256
255
 
257
256
  end
257
+
258
+ private
259
+
260
+ def remove_method_from_definition_target
261
+ definition_target.__send__(:remove_method, @method_name)
262
+ rescue NameError
263
+ # This can happen when the method has been monkeyed with by
264
+ # something outside RSpec. This happens, for example, when
265
+ # `file.write` has been stubbed, and then `file.reopen(other_io)`
266
+ # is later called, as `File#reopen` appears to redefine `write`.
267
+ #
268
+ # Note: we could avoid rescuing this by checking
269
+ # `definition_target.instance_method(@method_name).owner == definition_target`,
270
+ # saving us from the cost of the expensive exception, but this error is
271
+ # extremely rare (it was discovered on 2014-12-30, only happens on
272
+ # RUBY_VERSION < 2.0 and our spec suite only hits this condition once),
273
+ # so we'd rather avoid the cost of that check for every method double,
274
+ # and risk the rare situation where this exception will get raised.
275
+ RSpec.warn_with(
276
+ "WARNING: RSpec could not fully restore #{@object.inspect}." \
277
+ "#{@method_name}, possibly because the method has been redefined " \
278
+ "by something outside of RSpec."
279
+ )
280
+ end
258
281
  end
259
282
  end
260
283
  end
@@ -13,6 +13,8 @@ module RSpec
13
13
  @previously_defined = false
14
14
  @stubbed = false
15
15
  @hidden = false
16
+ @valid_name = true
17
+ yield self if block_given?
16
18
  end
17
19
 
18
20
  # @return [String] The fully qualified name of the constant.
@@ -24,7 +26,7 @@ module RSpec
24
26
  attr_accessor :original_value
25
27
 
26
28
  # @private
27
- attr_writer :previously_defined, :stubbed, :hidden
29
+ attr_writer :previously_defined, :stubbed, :hidden, :valid_name
28
30
 
29
31
  # @return [Boolean] Whether or not the constant was defined
30
32
  # before the current example.
@@ -50,6 +52,12 @@ module RSpec
50
52
  @hidden
51
53
  end
52
54
 
55
+ # @return [Boolean] Whether or not the provided constant name
56
+ # is a valid Ruby constant name.
57
+ def valid_name?
58
+ @valid_name
59
+ end
60
+
53
61
  # The default `to_s` isn't very useful, so a custom version is provided.
54
62
  def to_s
55
63
  "#<#{self.class.name} #{name}>"
@@ -58,13 +66,16 @@ module RSpec
58
66
 
59
67
  # @private
60
68
  def self.unmutated(name)
61
- const = new(name)
62
- const.previously_defined = recursive_const_defined?(name)
63
- const.stubbed = false
64
- const.hidden = false
65
- const.original_value = recursive_const_get(name) if const.previously_defined?
66
-
67
- const
69
+ previously_defined = recursive_const_defined?(name)
70
+ rescue NameError
71
+ new(name) do |c|
72
+ c.valid_name = false
73
+ end
74
+ else
75
+ new(name) do |const|
76
+ const.previously_defined = previously_defined
77
+ const.original_value = recursive_const_get(name) if previously_defined
78
+ end
68
79
  end
69
80
 
70
81
  # Queries rspec-mocks to find out information about the named constant.
@@ -6,8 +6,17 @@ module RSpec
6
6
  # on the given argument.
7
7
  def self.for(object_module_or_name, allow_direct_object_refs=false)
8
8
  case object_module_or_name
9
- when Module then DirectModuleReference.new(object_module_or_name)
10
- when String then NamedObjectReference.new(object_module_or_name)
9
+ when Module
10
+ if anonymous_module?(object_module_or_name)
11
+ DirectObjectReference.new(object_module_or_name)
12
+ else
13
+ # Use a `NamedObjectReference` if it has a name because this
14
+ # will use the original value of the constant in case it has
15
+ # been stubbed.
16
+ NamedObjectReference.new(name_of(object_module_or_name))
17
+ end
18
+ when String
19
+ NamedObjectReference.new(object_module_or_name)
11
20
  else
12
21
  if allow_direct_object_refs
13
22
  DirectObjectReference.new(object_module_or_name)
@@ -17,74 +26,123 @@ module RSpec
17
26
  end
18
27
  end
19
28
  end
29
+
30
+ if Module.new.name.nil?
31
+ def self.anonymous_module?(mod)
32
+ !name_of(mod)
33
+ end
34
+ else # 1.8.7
35
+ def self.anonymous_module?(mod)
36
+ name_of(mod) == ""
37
+ end
38
+ end
39
+ private_class_method :anonymous_module?
40
+
41
+ def self.name_of(mod)
42
+ MODULE_NAME_METHOD.bind(mod).call
43
+ end
44
+ private_class_method :name_of
45
+
46
+ # @private
47
+ MODULE_NAME_METHOD = Module.instance_method(:name)
20
48
  end
21
49
 
22
- # Used when an object is passed to `object_double`.
50
+ # An implementation of rspec-mocks' reference interface.
51
+ # Used when an object is passed to {ExampleMethods#object_double}, or
52
+ # an anonymous class or module is passed to {ExampleMethods#instance_double}
53
+ # or {ExampleMethods#class_double}.
23
54
  # Represents a reference to that object.
24
- #
25
- # @private
55
+ # @see NamedObjectReference
26
56
  class DirectObjectReference
57
+ # @param object [Object] the object to which this refers
27
58
  def initialize(object)
28
59
  @object = object
29
60
  end
30
61
 
62
+ # @return [String] the object's description (via `#inspect`).
31
63
  def description
32
64
  @object.inspect
33
65
  end
34
66
 
67
+ # Defined for interface parity with the other object reference
68
+ # implementations. Raises an `ArgumentError` to indicate that `as_stubbed_const`
69
+ # is invalid when passing an object argument to `object_double`.
35
70
  def const_to_replace
36
71
  raise ArgumentError,
37
- "Can not perform constant replacement with an object."
72
+ "Can not perform constant replacement with an anonymous object."
38
73
  end
39
74
 
75
+ # The target of the verifying double (the object itself).
76
+ #
77
+ # @return [Object]
78
+ def target
79
+ @object
80
+ end
81
+
82
+ # Always returns true for an object as the class is defined.
83
+ #
84
+ # @return [true]
40
85
  def defined?
41
86
  true
42
87
  end
43
88
 
89
+ # Yields if the reference target is loaded, providing a generic mechanism
90
+ # to optionally run a bit of code only when a reference's target is
91
+ # loaded.
92
+ #
93
+ # This specific implementation always yields because direct references
94
+ # are always loaded.
95
+ #
96
+ # @yield [Object] the target of this reference.
44
97
  def when_loaded
45
98
  yield @object
46
99
  end
47
100
  end
48
101
 
49
- # Used when a module is passed to `class_double` or `instance_double`.
50
- # Represents a reference to that module.
51
- #
52
- # @private
53
- class DirectModuleReference < DirectObjectReference
54
- def const_to_replace
55
- @object.name
56
- end
57
- alias description const_to_replace
58
- end
59
-
60
- # Used when a string is passed to `class_double`, `instance_double`
61
- # or `object_double`.
102
+ # An implementation of rspec-mocks' reference interface.
103
+ # Used when a string is passed to {ExampleMethods#object_double},
104
+ # and when a string, named class or named module is passed to
105
+ # {ExampleMethods#instance_double}, or {ExampleMethods#class_double}.
62
106
  # Represents a reference to the object named (via a constant lookup)
63
107
  # by the string.
64
- #
65
- # @private
108
+ # @see DirectObjectReference
66
109
  class NamedObjectReference
110
+ # @param const_name [String] constant name
67
111
  def initialize(const_name)
68
112
  @const_name = const_name
69
113
  end
70
114
 
115
+ # @return [Boolean] true if the named constant is defined, false otherwise.
71
116
  def defined?
72
117
  !!object
73
118
  end
74
119
 
120
+ # @return [String] the constant name to replace with a double.
75
121
  def const_to_replace
76
122
  @const_name
77
123
  end
78
124
  alias description const_to_replace
79
125
 
80
- def when_loaded(&_block)
126
+ # @return [Object, nil] the target of the verifying double (the named object), or
127
+ # nil if it is not defined.
128
+ def target
129
+ object
130
+ end
131
+
132
+ # Yields if the reference target is loaded, providing a generic mechanism
133
+ # to optionally run a bit of code only when a reference's target is
134
+ # loaded.
135
+ #
136
+ # @yield [Object] the target object
137
+ def when_loaded
81
138
  yield object if object
82
139
  end
83
140
 
84
141
  private
85
142
 
86
143
  def object
87
- @object ||= Constant.original(@const_name).original_value
144
+ return @object if defined?(@object)
145
+ @object = Constant.original(@const_name).original_value
88
146
  end
89
147
  end
90
148
  end
@@ -88,7 +88,12 @@ module RSpec
88
88
  @error_generator.raise_expectation_on_unstubbed_method(expected_method_name)
89
89
  end
90
90
 
91
- @messages_received.each do |(actual_method_name, args, _)|
91
+ @messages_received.each do |(actual_method_name, received_arg_list, _)|
92
+ if expectation.message == actual_method_name
93
+ expectation.fail_if_problematic_received_arg_mutations(received_arg_list)
94
+ end
95
+
96
+ args = received_arg_list.args
92
97
  next unless expectation.matches?(actual_method_name, *args)
93
98
 
94
99
  expectation.safe_invoke(nil)
@@ -98,7 +103,8 @@ module RSpec
98
103
 
99
104
  # @private
100
105
  def check_for_unexpected_arguments(expectation)
101
- @messages_received.each do |(method_name, args, _)|
106
+ @messages_received.each do |(method_name, received_arg_list, _)|
107
+ args = received_arg_list.args
102
108
  next unless expectation.matches_name_but_not_args(method_name, *args)
103
109
 
104
110
  raise_unexpected_message_args_error(expectation, *args)
@@ -138,7 +144,11 @@ module RSpec
138
144
 
139
145
  # @private
140
146
  def received_message?(method_name, *args, &block)
141
- @messages_received.any? { |array| array == [method_name, args, block] }
147
+ @messages_received.any? do |(received_method_name, received_arg_list, received_block)|
148
+ method_name == received_method_name &&
149
+ args == received_arg_list.args &&
150
+ block == received_block
151
+ end
142
152
  end
143
153
 
144
154
  # @private
@@ -149,7 +159,35 @@ module RSpec
149
159
  # @private
150
160
  def record_message_received(message, *args, &block)
151
161
  @order_group.invoked SpecificMessage.new(object, message, args)
152
- @messages_received << [message, args, block]
162
+ @messages_received << [message, ReceivedArgList.new(args), block]
163
+ end
164
+
165
+ class ReceivedArgList
166
+ attr_reader :args
167
+
168
+ def initialize(args)
169
+ @args = args
170
+ @original_hash = hash_of(args)
171
+ end
172
+
173
+ def has_mutations?
174
+ @original_hash != hash_of(args)
175
+ end
176
+
177
+ private
178
+
179
+ def hash_of(arg)
180
+ arg.hash
181
+ rescue Exception
182
+ # While `Object#hash` is a built-in ruby method that we expect args to
183
+ # support, there's no guarantee that all args will. For example, a
184
+ # `BasicObject` instance will raise a `NoMethodError`. Given that
185
+ # we use the hash only to advise the user of a rare case we don't
186
+ # support involving mutations, it seems better to ignore this error
187
+ # and use a static value in its place (which will make us assume no
188
+ # mutation has occurred).
189
+ :failed_to_get_hash
190
+ end
153
191
  end
154
192
 
155
193
  # @private
@@ -1,3 +1,3 @@
1
1
  require 'rspec/mocks'
2
- include RSpec::Mocks::ExampleMethods
2
+ extend RSpec::Mocks::ExampleMethods
3
3
  RSpec::Mocks.setup
@@ -28,6 +28,8 @@ module RSpec
28
28
  else
29
29
  __mock_proxy.ensure_publicly_implemented(message, self)
30
30
  end
31
+
32
+ __mock_proxy.validate_arguments!(message, args)
31
33
  end
32
34
 
33
35
  super
@@ -47,8 +49,17 @@ module RSpec
47
49
  __send__(name, *args, &block)
48
50
  end
49
51
 
50
- def initialize(*args)
51
- super
52
+ def initialize(doubled_module, *args)
53
+ @doubled_module = doubled_module
54
+
55
+ possible_name = args.first
56
+ name = if String === possible_name || Symbol === possible_name
57
+ args.shift
58
+ else
59
+ @description
60
+ end
61
+
62
+ super(name, *args)
52
63
  @__sending_message = nil
53
64
  end
54
65
  end
@@ -63,12 +74,8 @@ module RSpec
63
74
  include VerifyingDouble
64
75
 
65
76
  def initialize(doubled_module, *args)
66
- @doubled_module = doubled_module
67
-
68
- super(
69
- "#{doubled_module.description} (instance)",
70
- *args
71
- )
77
+ @description = "#{doubled_module.description} (instance)"
78
+ super
72
79
  end
73
80
 
74
81
  def __build_mock_proxy(order_group)
@@ -95,8 +102,8 @@ module RSpec
95
102
  private
96
103
 
97
104
  def initialize(doubled_module, *args)
98
- @doubled_module = doubled_module
99
- super(doubled_module.description, *args)
105
+ @description = doubled_module.description
106
+ super
100
107
  end
101
108
 
102
109
  def __build_mock_proxy(order_group)
@@ -23,31 +23,23 @@ module RSpec
23
23
 
24
24
  # @private
25
25
  def with(*args, &block)
26
- unless ArgumentMatchers::AnyArgsMatcher === args.first
27
- expected_args = if ArgumentMatchers::NoArgsMatcher === args.first
28
- []
29
- elsif args.length > 0
30
- args
31
- else
32
- # No arguments given, this will raise.
33
- super
34
- end
35
-
36
- validate_expected_arguments!(expected_args)
26
+ super(*args, &block).tap do
27
+ validate_expected_arguments! do |signature|
28
+ example_call_site_args = [:an_arg] * signature.min_non_kw_args
29
+ example_call_site_args << :kw_args_hash if signature.required_kw_args.any?
30
+ @argument_list_matcher.resolve_expected_args_based_on(example_call_site_args)
31
+ end
37
32
  end
38
- super
39
33
  end
40
34
 
41
35
  private
42
36
 
43
- def validate_expected_arguments!(actual_args)
37
+ def validate_expected_arguments!
44
38
  return if method_reference.nil?
45
39
 
46
40
  method_reference.with_signature do |signature|
47
- verifier = Support::LooseSignatureVerifier.new(
48
- signature,
49
- actual_args
50
- )
41
+ args = yield signature
42
+ verifier = Support::LooseSignatureVerifier.new(signature, args)
51
43
 
52
44
  unless verifier.valid?
53
45
  # Fail fast is required, otherwise the message expecation will fail
@@ -78,6 +78,10 @@ module RSpec
78
78
  def visibility_for(method_name)
79
79
  method_reference[method_name].visibility
80
80
  end
81
+
82
+ def validate_arguments!(method_name, args)
83
+ @method_doubles[method_name].validate_arguments!(args)
84
+ end
81
85
  end
82
86
 
83
87
  # @private
@@ -131,8 +135,6 @@ module RSpec
131
135
  super
132
136
  end
133
137
 
134
- private
135
-
136
138
  def validate_arguments!(actual_args)
137
139
  @method_reference.with_signature do |signature|
138
140
  verifier = Support::StrictSignatureVerifier.new(signature, actual_args)