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