rspec-mocks 2.11.3 → 2.12.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.
- data/Changelog.md +16 -0
- data/README.md +26 -1
- data/features/argument_matchers/explicit.feature +2 -2
- data/features/argument_matchers/general_matchers.feature +4 -4
- data/features/argument_matchers/type_matchers.feature +1 -1
- data/features/message_expectations/README.md +4 -0
- data/features/message_expectations/any_instance.feature +1 -1
- data/features/message_expectations/call_original.feature +24 -0
- data/features/message_expectations/expect_message.feature +5 -5
- data/features/message_expectations/receive_counts.feature +7 -7
- data/features/message_expectations/warn_when_expectation_is_set_on_nil.feature +3 -3
- data/features/method_stubs/any_instance.feature +6 -7
- data/features/method_stubs/as_null_object.feature +1 -1
- data/features/method_stubs/simple_return_value.feature +2 -2
- data/features/method_stubs/stub_chain.feature +1 -1
- data/features/method_stubs/stub_implementation.feature +1 -1
- data/features/method_stubs/to_ary.feature +1 -1
- data/features/{stubbing_constants → mutating_constants}/README.md +21 -1
- data/features/mutating_constants/hiding_defined_constant.feature +64 -0
- data/features/{stubbing_constants → mutating_constants}/stub_defined_constant.feature +0 -0
- data/features/{stubbing_constants → mutating_constants}/stub_undefined_constant.feature +0 -0
- data/features/outside_rspec/configuration.feature +3 -3
- data/features/outside_rspec/standalone.feature +6 -5
- data/lib/rspec/mocks.rb +17 -1
- data/lib/rspec/mocks/any_instance.rb +12 -12
- data/lib/rspec/mocks/configuration.rb +28 -0
- data/lib/rspec/mocks/error_generator.rb +6 -0
- data/lib/rspec/mocks/example_methods.rb +34 -9
- data/lib/rspec/mocks/framework.rb +3 -2
- data/lib/rspec/mocks/instance_method_stasher.rb +70 -0
- data/lib/rspec/mocks/message_expectation.rb +49 -29
- data/lib/rspec/mocks/method_double.rb +84 -7
- data/lib/rspec/mocks/{stub_const.rb → mutate_const.rb} +117 -40
- data/lib/rspec/mocks/proxy.rb +16 -5
- data/lib/rspec/mocks/version.rb +1 -1
- data/spec/rspec/mocks/and_call_original_spec.rb +162 -0
- data/spec/rspec/mocks/any_instance_spec.rb +18 -7
- data/spec/rspec/mocks/configuration_spec.rb +26 -0
- data/spec/rspec/mocks/failing_argument_matchers_spec.rb +9 -10
- data/spec/rspec/mocks/instance_method_stasher_spec.rb +58 -0
- data/spec/rspec/mocks/mock_spec.rb +35 -35
- data/spec/rspec/mocks/{stub_const_spec.rb → mutate_const_spec.rb} +142 -13
- data/spec/rspec/mocks/null_object_mock_spec.rb +3 -2
- data/spec/rspec/mocks/partial_mock_spec.rb +102 -77
- data/spec/rspec/mocks/serialization_spec.rb +1 -2
- data/spec/rspec/mocks/stub_implementation_spec.rb +6 -6
- data/spec/rspec/mocks_spec.rb +7 -0
- data/spec/spec_helper.rb +11 -0
- metadata +79 -80
- data/lib/rspec/mocks/stashed_instance_method.rb +0 -60
- data/spec/rspec/mocks/stashed_instance_method_spec.rb +0 -53
@@ -3,7 +3,7 @@ module RSpec
|
|
3
3
|
# @private
|
4
4
|
class MethodDouble < Hash
|
5
5
|
# @private
|
6
|
-
attr_reader :method_name
|
6
|
+
attr_reader :method_name, :object
|
7
7
|
|
8
8
|
# @private
|
9
9
|
def initialize(object, method_name, proxy)
|
@@ -11,7 +11,7 @@ module RSpec
|
|
11
11
|
@object = object
|
12
12
|
@proxy = proxy
|
13
13
|
|
14
|
-
@
|
14
|
+
@method_stasher = InstanceMethodStasher.new(object_singleton_class, @method_name)
|
15
15
|
@method_is_proxied = false
|
16
16
|
store(:expectations, [])
|
17
17
|
store(:stubs, [])
|
@@ -40,6 +40,80 @@ module RSpec
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
# @private
|
44
|
+
def original_method
|
45
|
+
if @method_stasher.method_is_stashed?
|
46
|
+
# Example: a singleton method defined on @object
|
47
|
+
method_handle_for(@object, @method_stasher.stashed_method_name)
|
48
|
+
else
|
49
|
+
begin
|
50
|
+
# Example: an instance method defined on @object's class.
|
51
|
+
@object.class.instance_method(@method_name).bind(@object)
|
52
|
+
rescue NameError
|
53
|
+
raise unless @object.respond_to?(:superclass)
|
54
|
+
|
55
|
+
# Example: a singleton method defined on @object's superclass.
|
56
|
+
#
|
57
|
+
# Note: we have to give precedence to instance methods
|
58
|
+
# defined on @object's class, because in a case like:
|
59
|
+
#
|
60
|
+
# `klass.should_receive(:new).and_call_original`
|
61
|
+
#
|
62
|
+
# ...we want `Class#new` bound to `klass` (which will return
|
63
|
+
# an instance of `klass`), not `klass.superclass.new` (which
|
64
|
+
# would return an instance of `klass.superclass`).
|
65
|
+
original_method_from_superclass
|
66
|
+
end
|
67
|
+
end
|
68
|
+
rescue NameError
|
69
|
+
# We have no way of knowing if the object's method_missing
|
70
|
+
# will handle this message or not...but we can at least try.
|
71
|
+
# If it's not handled, a `NoMethodError` will be raised, just
|
72
|
+
# like normally.
|
73
|
+
Proc.new do |*args, &block|
|
74
|
+
@object.__send__(:method_missing, @method_name, *args, &block)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if RUBY_VERSION.to_f > 1.8
|
79
|
+
# @private
|
80
|
+
def original_method_from_superclass
|
81
|
+
@object.superclass.
|
82
|
+
singleton_class.
|
83
|
+
instance_method(@method_name).
|
84
|
+
bind(@object)
|
85
|
+
end
|
86
|
+
else
|
87
|
+
# Our implementation for 1.9 (above) causes an error on 1.8:
|
88
|
+
# TypeError: singleton method bound for a different object
|
89
|
+
#
|
90
|
+
# This doesn't work quite right in all circumstances but it's the
|
91
|
+
# best we can do.
|
92
|
+
# @private
|
93
|
+
def original_method_from_superclass
|
94
|
+
::Kernel.warn <<-WARNING.gsub(/^ +\|/, '')
|
95
|
+
|
|
96
|
+
|WARNING: On ruby 1.8, rspec-mocks is unable to bind the original
|
97
|
+
|`#{@method_name}` method to your partial mock object (#{@object})
|
98
|
+
|for `and_call_original`. The superclass's `#{@method_name}` is being
|
99
|
+
|used instead; however, it may not work correctly when executed due
|
100
|
+
|to the fact that `self` will be #{@object.superclass}, not #{@object}.
|
101
|
+
|
|
102
|
+
|Called from: #{caller[2]}
|
103
|
+
WARNING
|
104
|
+
|
105
|
+
@object.superclass.method(@method_name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# @private
|
110
|
+
OBJECT_METHOD_METHOD = ::Object.instance_method(:method)
|
111
|
+
|
112
|
+
# @private
|
113
|
+
def method_handle_for(object, method_name)
|
114
|
+
OBJECT_METHOD_METHOD.bind(object).call(method_name)
|
115
|
+
end
|
116
|
+
|
43
117
|
# @private
|
44
118
|
def object_singleton_class
|
45
119
|
class << @object; self; end
|
@@ -49,7 +123,7 @@ module RSpec
|
|
49
123
|
def configure_method
|
50
124
|
RSpec::Mocks::space.add(@object) if RSpec::Mocks::space
|
51
125
|
warn_if_nil_class
|
52
|
-
@
|
126
|
+
@method_stasher.stash unless @method_is_proxied
|
53
127
|
define_proxy_method
|
54
128
|
end
|
55
129
|
|
@@ -76,7 +150,7 @@ module RSpec
|
|
76
150
|
return unless @method_is_proxied
|
77
151
|
|
78
152
|
object_singleton_class.__send__(:remove_method, @method_name)
|
79
|
-
@
|
153
|
+
@method_stasher.restore
|
80
154
|
@method_is_proxied = false
|
81
155
|
end
|
82
156
|
|
@@ -104,7 +178,8 @@ module RSpec
|
|
104
178
|
expectation = if existing_stub = stubs.first
|
105
179
|
existing_stub.build_child(expected_from, 1, opts, &implementation)
|
106
180
|
else
|
107
|
-
MessageExpectation.new(error_generator, expectation_ordering,
|
181
|
+
MessageExpectation.new(error_generator, expectation_ordering,
|
182
|
+
expected_from, self, 1, opts, &implementation)
|
108
183
|
end
|
109
184
|
expectations << expectation
|
110
185
|
expectation
|
@@ -113,7 +188,8 @@ module RSpec
|
|
113
188
|
# @private
|
114
189
|
def add_negative_expectation(error_generator, expectation_ordering, expected_from, &implementation)
|
115
190
|
configure_method
|
116
|
-
expectation = NegativeMessageExpectation.new(error_generator, expectation_ordering,
|
191
|
+
expectation = NegativeMessageExpectation.new(error_generator, expectation_ordering,
|
192
|
+
expected_from, self, &implementation)
|
117
193
|
expectations.unshift expectation
|
118
194
|
expectation
|
119
195
|
end
|
@@ -121,7 +197,8 @@ module RSpec
|
|
121
197
|
# @private
|
122
198
|
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
|
123
199
|
configure_method
|
124
|
-
stub = MessageExpectation.new(error_generator, expectation_ordering, expected_from,
|
200
|
+
stub = MessageExpectation.new(error_generator, expectation_ordering, expected_from,
|
201
|
+
self, :any, opts, &implementation)
|
125
202
|
stubs.unshift stub
|
126
203
|
stub
|
127
204
|
end
|
@@ -17,14 +17,14 @@ module RSpec
|
|
17
17
|
# On 1.8:
|
18
18
|
# - C.const_get("Hash") # => ::Hash
|
19
19
|
# - C.const_defined?("Hash") # => false
|
20
|
-
# - C.constants # => ["
|
20
|
+
# - C.constants # => ["B"]
|
21
21
|
# - None of these methods accept the extra `inherit` argument
|
22
22
|
# On 1.9:
|
23
23
|
# - C.const_get("Hash") # => ::Hash
|
24
24
|
# - C.const_defined?("Hash") # => true
|
25
25
|
# - C.const_get("Hash", false) # => raises NameError
|
26
26
|
# - C.const_defined?("Hash", false) # => false
|
27
|
-
# - C.constants # => [:
|
27
|
+
# - C.constants # => [:B]
|
28
28
|
# - C.constants(false) #=> []
|
29
29
|
if Module.method(:const_defined?).arity == 1
|
30
30
|
def const_defined_on?(mod, const_name)
|
@@ -70,7 +70,7 @@ module RSpec
|
|
70
70
|
end
|
71
71
|
|
72
72
|
# Provides information about constants that may (or may not)
|
73
|
-
# have been
|
73
|
+
# have been mutated by rspec-mocks.
|
74
74
|
class Constant
|
75
75
|
extend RecursiveConstMethods
|
76
76
|
|
@@ -83,12 +83,12 @@ module RSpec
|
|
83
83
|
attr_reader :name
|
84
84
|
|
85
85
|
# @return [Object, nil] The original value (e.g. before it
|
86
|
-
# was
|
86
|
+
# was mutated by rspec-mocks) of the constant, or
|
87
87
|
# nil if the constant was not previously defined.
|
88
88
|
attr_accessor :original_value
|
89
89
|
|
90
90
|
# @api private
|
91
|
-
attr_writer :previously_defined, :stubbed
|
91
|
+
attr_writer :previously_defined, :stubbed, :hidden
|
92
92
|
|
93
93
|
# @return [Boolean] Whether or not the constant was defined
|
94
94
|
# before the current example.
|
@@ -96,27 +96,40 @@ module RSpec
|
|
96
96
|
@previously_defined
|
97
97
|
end
|
98
98
|
|
99
|
+
# @return [Boolean] Whether or not rspec-mocks has mutated
|
100
|
+
# (stubbed or hidden) this constant.
|
101
|
+
def mutated?
|
102
|
+
@stubbed || @hidden
|
103
|
+
end
|
104
|
+
|
99
105
|
# @return [Boolean] Whether or not rspec-mocks has stubbed
|
100
106
|
# this constant.
|
101
107
|
def stubbed?
|
102
108
|
@stubbed
|
103
109
|
end
|
104
110
|
|
111
|
+
# @return [Boolean] Whether or not rspec-mocks has hidden
|
112
|
+
# this constant.
|
113
|
+
def hidden?
|
114
|
+
@hidden
|
115
|
+
end
|
116
|
+
|
105
117
|
def to_s
|
106
118
|
"#<#{self.class.name} #{name}>"
|
107
119
|
end
|
108
120
|
alias inspect to_s
|
109
121
|
|
110
122
|
# @api private
|
111
|
-
def self.
|
123
|
+
def self.unmutated(name)
|
112
124
|
const = new(name)
|
113
125
|
const.previously_defined = recursive_const_defined?(name)
|
114
126
|
const.stubbed = false
|
127
|
+
const.hidden = false
|
115
128
|
const.original_value = recursive_const_get(name) if const.previously_defined?
|
116
129
|
|
117
130
|
const
|
118
131
|
end
|
119
|
-
private_class_method :
|
132
|
+
private_class_method :unmutated
|
120
133
|
|
121
134
|
# Queries rspec-mocks to find out information about the named constant.
|
122
135
|
#
|
@@ -124,13 +137,13 @@ module RSpec
|
|
124
137
|
# @return [Constant] an object contaning information about the named
|
125
138
|
# constant.
|
126
139
|
def self.original(name)
|
127
|
-
|
128
|
-
|
140
|
+
mutator = ConstantMutator.find(name)
|
141
|
+
mutator ? mutator.to_constant : unmutated(name)
|
129
142
|
end
|
130
143
|
end
|
131
144
|
|
132
145
|
# Provides a means to stub constants.
|
133
|
-
class
|
146
|
+
class ConstantMutator
|
134
147
|
extend RecursiveConstMethods
|
135
148
|
|
136
149
|
# Stubs a constant.
|
@@ -145,31 +158,43 @@ module RSpec
|
|
145
158
|
# so you can stub constants in other contexts (e.g. helper
|
146
159
|
# classes).
|
147
160
|
def self.stub(constant_name, value, options = {})
|
148
|
-
|
161
|
+
mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
|
149
162
|
DefinedConstantReplacer
|
150
163
|
else
|
151
164
|
UndefinedConstantSetter
|
152
165
|
end
|
153
166
|
|
154
|
-
|
155
|
-
stubbers << stubber
|
156
|
-
|
157
|
-
stubber.stub
|
158
|
-
ensure_registered_with_mocks_space
|
167
|
+
mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
|
159
168
|
value
|
160
169
|
end
|
161
170
|
|
162
|
-
#
|
171
|
+
# Hides a constant.
|
172
|
+
#
|
173
|
+
# @param (see ExampleMethods#hide_const)
|
174
|
+
#
|
175
|
+
# @see ExampleMethods#hide_const
|
176
|
+
# @note It's recommended that you use `hide_const` in your
|
177
|
+
# examples. This is an alternate public API that is provided
|
178
|
+
# so you can hide constants in other contexts (e.g. helper
|
179
|
+
# classes).
|
180
|
+
def self.hide(constant_name)
|
181
|
+
return unless recursive_const_defined?(constant_name)
|
182
|
+
|
183
|
+
mutate(ConstantHider.new(constant_name, nil, { }))
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
|
187
|
+
# Contains common functionality used by all of the constant mutators.
|
163
188
|
#
|
164
189
|
# @api private
|
165
|
-
class
|
190
|
+
class BaseMutator
|
166
191
|
include RecursiveConstMethods
|
167
192
|
|
168
193
|
attr_reader :original_value, :full_constant_name
|
169
194
|
|
170
|
-
def initialize(full_constant_name,
|
195
|
+
def initialize(full_constant_name, mutated_value, transfer_nested_constants)
|
171
196
|
@full_constant_name = full_constant_name
|
172
|
-
@
|
197
|
+
@mutated_value = mutated_value
|
173
198
|
@transfer_nested_constants = transfer_nested_constants
|
174
199
|
@context_parts = @full_constant_name.split('::')
|
175
200
|
@const_name = @context_parts.pop
|
@@ -177,32 +202,58 @@ module RSpec
|
|
177
202
|
|
178
203
|
def to_constant
|
179
204
|
const = Constant.new(full_constant_name)
|
180
|
-
const.stubbed = true
|
181
|
-
const.previously_defined = previously_defined?
|
182
205
|
const.original_value = original_value
|
183
206
|
|
184
207
|
const
|
185
208
|
end
|
186
209
|
end
|
187
210
|
|
211
|
+
# Hides a defined constant for the duration of an example.
|
212
|
+
#
|
213
|
+
# @api private
|
214
|
+
class ConstantHider < BaseMutator
|
215
|
+
def mutate
|
216
|
+
@context = recursive_const_get(@context_parts.join('::'))
|
217
|
+
@original_value = get_const_defined_on(@context, @const_name)
|
218
|
+
|
219
|
+
@context.send(:remove_const, @const_name)
|
220
|
+
end
|
221
|
+
|
222
|
+
def to_constant
|
223
|
+
const = super
|
224
|
+
const.hidden = true
|
225
|
+
const.previously_defined = true
|
226
|
+
|
227
|
+
const
|
228
|
+
end
|
229
|
+
|
230
|
+
def rspec_reset
|
231
|
+
@context.const_set(@const_name, @original_value)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
188
235
|
# Replaces a defined constant for the duration of an example.
|
189
236
|
#
|
190
237
|
# @api private
|
191
|
-
class DefinedConstantReplacer <
|
192
|
-
def
|
238
|
+
class DefinedConstantReplacer < BaseMutator
|
239
|
+
def mutate
|
193
240
|
@context = recursive_const_get(@context_parts.join('::'))
|
194
241
|
@original_value = get_const_defined_on(@context, @const_name)
|
195
242
|
|
196
243
|
constants_to_transfer = verify_constants_to_transfer!
|
197
244
|
|
198
245
|
@context.send(:remove_const, @const_name)
|
199
|
-
@context.const_set(@const_name, @
|
246
|
+
@context.const_set(@const_name, @mutated_value)
|
200
247
|
|
201
248
|
transfer_nested_constants(constants_to_transfer)
|
202
249
|
end
|
203
250
|
|
204
|
-
def
|
205
|
-
|
251
|
+
def to_constant
|
252
|
+
const = super
|
253
|
+
const.stubbed = true
|
254
|
+
const.previously_defined = true
|
255
|
+
|
256
|
+
const
|
206
257
|
end
|
207
258
|
|
208
259
|
def rspec_reset
|
@@ -212,14 +263,14 @@ module RSpec
|
|
212
263
|
|
213
264
|
def transfer_nested_constants(constants)
|
214
265
|
constants.each do |const|
|
215
|
-
@
|
266
|
+
@mutated_value.const_set(const, get_const_defined_on(original_value, const))
|
216
267
|
end
|
217
268
|
end
|
218
269
|
|
219
270
|
def verify_constants_to_transfer!
|
220
271
|
return [] unless @transfer_nested_constants
|
221
272
|
|
222
|
-
{ @original_value => "the original value", @
|
273
|
+
{ @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
|
223
274
|
unless value.respond_to?(:constants)
|
224
275
|
raise ArgumentError,
|
225
276
|
"Cannot transfer nested constants for #{@full_constant_name} " +
|
@@ -250,8 +301,8 @@ module RSpec
|
|
250
301
|
# Sets an undefined constant for the duration of an example.
|
251
302
|
#
|
252
303
|
# @api private
|
253
|
-
class UndefinedConstantSetter <
|
254
|
-
def
|
304
|
+
class UndefinedConstantSetter < BaseMutator
|
305
|
+
def mutate
|
255
306
|
remaining_parts = @context_parts.dup
|
256
307
|
@deepest_defined_const = @context_parts.inject(Object) do |klass, name|
|
257
308
|
break klass unless const_defined_on?(klass, name)
|
@@ -264,11 +315,15 @@ module RSpec
|
|
264
315
|
end
|
265
316
|
|
266
317
|
@const_to_remove = remaining_parts.first || @const_name
|
267
|
-
context.const_set(@const_name, @
|
318
|
+
context.const_set(@const_name, @mutated_value)
|
268
319
|
end
|
269
320
|
|
270
|
-
def
|
271
|
-
|
321
|
+
def to_constant
|
322
|
+
const = super
|
323
|
+
const.stubbed = true
|
324
|
+
const.previously_defined = false
|
325
|
+
|
326
|
+
const
|
272
327
|
end
|
273
328
|
|
274
329
|
def rspec_reset
|
@@ -276,6 +331,17 @@ module RSpec
|
|
276
331
|
end
|
277
332
|
end
|
278
333
|
|
334
|
+
# Uses the mutator to mutate (stub or hide) a constant. Ensures that
|
335
|
+
# the mutator is correctly registered so it can be backed out at the end
|
336
|
+
# of the test.
|
337
|
+
#
|
338
|
+
# @api private
|
339
|
+
def self.mutate(mutator)
|
340
|
+
register_mutator(mutator)
|
341
|
+
mutator.mutate
|
342
|
+
ensure_registered_with_mocks_space
|
343
|
+
end
|
344
|
+
|
279
345
|
# Ensures the constant stubbing is registered with
|
280
346
|
# rspec-mocks space so that stubbed constants can
|
281
347
|
# be restored when examples finish.
|
@@ -297,21 +363,26 @@ module RSpec
|
|
297
363
|
# We use reverse order so that if the same constant
|
298
364
|
# was stubbed multiple times, the original value gets
|
299
365
|
# properly restored.
|
300
|
-
|
366
|
+
mutators.reverse.each { |s| s.rspec_reset }
|
301
367
|
|
302
|
-
|
368
|
+
mutators.clear
|
303
369
|
end
|
304
370
|
|
305
|
-
# The list of constant
|
371
|
+
# The list of constant mutators that have been used for
|
306
372
|
# the current example.
|
307
373
|
#
|
308
374
|
# @api private
|
309
|
-
def self.
|
310
|
-
@
|
375
|
+
def self.mutators
|
376
|
+
@mutators ||= []
|
377
|
+
end
|
378
|
+
|
379
|
+
# @api private
|
380
|
+
def self.register_mutator(mutator)
|
381
|
+
mutators << mutator
|
311
382
|
end
|
312
383
|
|
313
384
|
def self.find(name)
|
314
|
-
|
385
|
+
mutators.find { |s| s.full_constant_name == name }
|
315
386
|
end
|
316
387
|
|
317
388
|
# Used internally by the constant stubbing to raise a helpful
|
@@ -327,6 +398,12 @@ module RSpec
|
|
327
398
|
end
|
328
399
|
end
|
329
400
|
end
|
401
|
+
|
402
|
+
# Keeps backwards compatibility since we had released an rspec-mocks that
|
403
|
+
# only supported stubbing. Later, we released the hide_const feature and
|
404
|
+
# decided that the term "mutator" was a better term to wrap up the concept
|
405
|
+
# of both stubbing and hiding.
|
406
|
+
ConstantStubber = ConstantMutator
|
330
407
|
end
|
331
408
|
end
|
332
409
|
|