rspec-mocks 2.10.1 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/Changelog.md +26 -1
  2. data/README.md +14 -2
  3. data/features/stubbing_constants/README.md +62 -0
  4. data/features/stubbing_constants/stub_defined_constant.feature +79 -0
  5. data/features/stubbing_constants/stub_undefined_constant.feature +50 -0
  6. data/lib/rspec/mocks/any_instance.rb +37 -1
  7. data/lib/rspec/mocks/any_instance/chain.rb +0 -81
  8. data/lib/rspec/mocks/any_instance/expectation_chain.rb +57 -0
  9. data/lib/rspec/mocks/any_instance/recorder.rb +6 -1
  10. data/lib/rspec/mocks/any_instance/stub_chain.rb +37 -0
  11. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +25 -0
  12. data/lib/rspec/mocks/argument_list_matcher.rb +93 -0
  13. data/lib/rspec/mocks/argument_matchers.rb +39 -31
  14. data/lib/rspec/mocks/error_generator.rb +7 -0
  15. data/lib/rspec/mocks/example_methods.rb +41 -0
  16. data/lib/rspec/mocks/framework.rb +2 -1
  17. data/lib/rspec/mocks/message_expectation.rb +21 -13
  18. data/lib/rspec/mocks/methods.rb +4 -0
  19. data/lib/rspec/mocks/proxy.rb +10 -4
  20. data/lib/rspec/mocks/stub_const.rb +280 -0
  21. data/lib/rspec/mocks/test_double.rb +3 -2
  22. data/lib/rspec/mocks/version.rb +1 -1
  23. data/spec/rspec/mocks/any_instance/message_chains_spec.rb +1 -1
  24. data/spec/rspec/mocks/any_instance_spec.rb +66 -26
  25. data/spec/rspec/mocks/argument_expectation_spec.rb +7 -7
  26. data/spec/rspec/mocks/at_least_spec.rb +14 -0
  27. data/spec/rspec/mocks/block_return_value_spec.rb +8 -0
  28. data/spec/rspec/mocks/mock_spec.rb +33 -20
  29. data/spec/rspec/mocks/multiple_return_value_spec.rb +2 -2
  30. data/spec/rspec/mocks/null_object_mock_spec.rb +22 -0
  31. data/spec/rspec/mocks/stub_chain_spec.rb +45 -45
  32. data/spec/rspec/mocks/stub_const_spec.rb +309 -0
  33. data/spec/rspec/mocks/stub_spec.rb +2 -2
  34. data/spec/spec_helper.rb +0 -40
  35. metadata +18 -6
  36. data/lib/rspec/mocks/argument_expectation.rb +0 -52
@@ -135,6 +135,10 @@ module RSpec
135
135
  mp
136
136
  end
137
137
  end
138
+
139
+ def __remove_mock_proxy
140
+ @mock_proxy = nil
141
+ end
138
142
 
139
143
  def format_chain(*chain, &blk)
140
144
  if Hash === chain.last
@@ -65,6 +65,7 @@ module RSpec
65
65
 
66
66
  # @private
67
67
  def add_message_expectation(location, method_name, opts={}, &block)
68
+ block ||= Proc.new { @object } if null_object?
68
69
  method_double[method_name].add_expectation @error_generator, @expectation_ordering, location, opts, &block
69
70
  end
70
71
 
@@ -128,7 +129,7 @@ module RSpec
128
129
  raise_unexpected_message_args_error(expectation, *args) unless (has_negative_expectation?(message) or null_object?)
129
130
  elsif stub = find_almost_matching_stub(message, *args)
130
131
  stub.advise(*args)
131
- raise_unexpected_message_args_error(stub, *args)
132
+ raise_missing_default_stub_error(stub, *args)
132
133
  elsif @object.is_a?(Class)
133
134
  @object.superclass.__send__(message, *args, &block)
134
135
  else
@@ -136,14 +137,19 @@ module RSpec
136
137
  end
137
138
  end
138
139
 
140
+ # @private
141
+ def raise_unexpected_message_error(method_name, *args)
142
+ @error_generator.raise_unexpected_message_error method_name, *args
143
+ end
144
+
139
145
  # @private
140
146
  def raise_unexpected_message_args_error(expectation, *args)
141
147
  @error_generator.raise_unexpected_message_args_error(expectation, *args)
142
148
  end
143
149
 
144
150
  # @private
145
- def raise_unexpected_message_error(method_name, *args)
146
- @error_generator.raise_unexpected_message_error method_name, *args
151
+ def raise_missing_default_stub_error(expectation, *args)
152
+ @error_generator.raise_missing_default_stub_error(expectation, *args)
147
153
  end
148
154
 
149
155
  private
@@ -157,7 +163,7 @@ module RSpec
157
163
  end
158
164
 
159
165
  def find_matching_expectation(method_name, *args)
160
- method_double[method_name].expectations.find {|expectation| expectation.matches?(method_name, *args) && !expectation.called_max_times?} ||
166
+ (method_double[method_name].expectations.find {|expectation| expectation.matches?(method_name, *args) && !expectation.called_max_times?}) ||
161
167
  method_double[method_name].expectations.find {|expectation| expectation.matches?(method_name, *args)}
162
168
  end
163
169
 
@@ -0,0 +1,280 @@
1
+ module RSpec
2
+ module Mocks
3
+ # Provides recursive constant lookup methods useful for
4
+ # constant stubbing.
5
+ # @api private
6
+ module RecursiveConstMethods
7
+ def recursive_const_get(name)
8
+ name.split('::').inject(Object) { |mod, name| mod.const_get name }
9
+ end
10
+
11
+ def recursive_const_defined?(name)
12
+ name.split('::').inject([Object, '']) do |(mod, full_name), name|
13
+ yield(full_name, name) if block_given? && !mod.is_a?(Module)
14
+ return false unless mod.const_defined?(name)
15
+ [mod.const_get(name), [mod, name].join('::')]
16
+ end
17
+ end
18
+ end
19
+
20
+ # Provides information about constants that may (or may not)
21
+ # have been stubbed by rspec-mocks.
22
+ class Constant
23
+ extend RecursiveConstMethods
24
+
25
+ # @api private
26
+ def initialize(name)
27
+ @name = name
28
+ end
29
+
30
+ # @return [String] The fully qualified name of the constant.
31
+ attr_reader :name
32
+
33
+ # @return [Object, nil] The original value (e.g. before it
34
+ # was stubbed by rspec-mocks) of the constant, or
35
+ # nil if the constant was not previously defined.
36
+ attr_accessor :original_value
37
+
38
+ # @api private
39
+ attr_writer :previously_defined, :stubbed
40
+
41
+ # @return [Boolean] Whether or not the constant was defined
42
+ # before the current example.
43
+ def previously_defined?
44
+ @previously_defined
45
+ end
46
+
47
+ # @return [Boolean] Whether or not rspec-mocks has stubbed
48
+ # this constant.
49
+ def stubbed?
50
+ @stubbed
51
+ end
52
+
53
+ def to_s
54
+ "#<#{self.class.name} #{name}>"
55
+ end
56
+ alias inspect to_s
57
+
58
+ # @api private
59
+ def self.unstubbed(name)
60
+ const = new(name)
61
+ const.previously_defined = recursive_const_defined?(name)
62
+ const.stubbed = false
63
+ const.original_value = recursive_const_get(name) if const.previously_defined?
64
+
65
+ const
66
+ end
67
+ private_class_method :unstubbed
68
+
69
+ # Queries rspec-mocks to find out information about the named constant.
70
+ #
71
+ # @param [String] name the name of the constant
72
+ # @return [Constant] an object contaning information about the named
73
+ # constant.
74
+ def self.original(name)
75
+ stubber = ConstantStubber.find(name)
76
+ stubber ? stubber.to_constant : unstubbed(name)
77
+ end
78
+ end
79
+
80
+ # Provides a means to stub constants.
81
+ class ConstantStubber
82
+ extend RecursiveConstMethods
83
+
84
+ # Stubs a constant.
85
+ #
86
+ # @param (see ExampleMethods#stub_const)
87
+ # @option (see ExampleMethods#stub_const)
88
+ # @return (see ExampleMethods#stub_const)
89
+ #
90
+ # @see ExampleMethods#stub_const
91
+ # @note It's recommended that you use `stub_const` in your
92
+ # examples. This is an alternate public API that is provided
93
+ # so you can stub constants in other contexts (e.g. helper
94
+ # classes).
95
+ def self.stub(constant_name, value, options = {})
96
+ stubber = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
97
+ DefinedConstantReplacer
98
+ else
99
+ UndefinedConstantSetter
100
+ end
101
+
102
+ stubber = stubber.new(constant_name, value, options[:transfer_nested_constants])
103
+ stubbers << stubber
104
+
105
+ stubber.stub
106
+ ensure_registered_with_mocks_space
107
+ value
108
+ end
109
+
110
+ # Contains common functionality used by both of the constant stubbers.
111
+ #
112
+ # @api private
113
+ class BaseStubber
114
+ include RecursiveConstMethods
115
+
116
+ attr_reader :original_value, :full_constant_name
117
+
118
+ def initialize(full_constant_name, stubbed_value, transfer_nested_constants)
119
+ @full_constant_name = full_constant_name
120
+ @stubbed_value = stubbed_value
121
+ @transfer_nested_constants = transfer_nested_constants
122
+ @context_parts = @full_constant_name.split('::')
123
+ @const_name = @context_parts.pop
124
+ end
125
+
126
+ def to_constant
127
+ const = Constant.new(full_constant_name)
128
+ const.stubbed = true
129
+ const.previously_defined = previously_defined?
130
+ const.original_value = original_value
131
+
132
+ const
133
+ end
134
+ end
135
+
136
+ # Replaces a defined constant for the duration of an example.
137
+ #
138
+ # @api private
139
+ class DefinedConstantReplacer < BaseStubber
140
+ def stub
141
+ @context = recursive_const_get(@context_parts.join('::'))
142
+ @original_value = @context.const_get(@const_name)
143
+
144
+ constants_to_transfer = verify_constants_to_transfer!
145
+
146
+ @context.send(:remove_const, @const_name)
147
+ @context.const_set(@const_name, @stubbed_value)
148
+
149
+ transfer_nested_constants(constants_to_transfer)
150
+ end
151
+
152
+ def previously_defined?
153
+ true
154
+ end
155
+
156
+ def rspec_reset
157
+ @context.send(:remove_const, @const_name)
158
+ @context.const_set(@const_name, @original_value)
159
+ end
160
+
161
+ def transfer_nested_constants(constants)
162
+ constants.each do |const|
163
+ @stubbed_value.const_set(const, original_value.const_get(const))
164
+ end
165
+ end
166
+
167
+ def verify_constants_to_transfer!
168
+ return [] unless @transfer_nested_constants
169
+
170
+ { @original_value => "the original value", @stubbed_value => "the stubbed value" }.each do |value, description|
171
+ unless value.respond_to?(:constants)
172
+ raise ArgumentError,
173
+ "Cannot transfer nested constants for #{@full_constant_name} " +
174
+ "since #{description} is not a class or module and only classes " +
175
+ "and modules support nested constants."
176
+ end
177
+ end
178
+
179
+ if @transfer_nested_constants.is_a?(Array)
180
+ @transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
181
+ undefined_constants = @transfer_nested_constants - @original_value.constants
182
+
183
+ if undefined_constants.any?
184
+ available_constants = @original_value.constants - @transfer_nested_constants
185
+ raise ArgumentError,
186
+ "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
187
+ "for #{@full_constant_name} since they are not defined. Did you mean " +
188
+ "#{available_constants.join(' or ')}?"
189
+ end
190
+
191
+ @transfer_nested_constants
192
+ else
193
+ @original_value.constants
194
+ end
195
+ end
196
+ end
197
+
198
+ # Sets an undefined constant for the duration of an example.
199
+ #
200
+ # @api private
201
+ class UndefinedConstantSetter < BaseStubber
202
+ def stub
203
+ remaining_parts = @context_parts.dup
204
+ @deepest_defined_const = @context_parts.inject(Object) do |klass, name|
205
+ break klass unless klass.const_defined?(name)
206
+ remaining_parts.shift
207
+ klass.const_get(name)
208
+ end
209
+
210
+ context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
211
+ klass.const_set(name, Module.new)
212
+ end
213
+
214
+ @const_to_remove = remaining_parts.first || @const_name
215
+ context.const_set(@const_name, @stubbed_value)
216
+ end
217
+
218
+ def previously_defined?
219
+ false
220
+ end
221
+
222
+ def rspec_reset
223
+ @deepest_defined_const.send(:remove_const, @const_to_remove)
224
+ end
225
+ end
226
+
227
+ # Ensures the constant stubbing is registered with
228
+ # rspec-mocks space so that stubbed constants can
229
+ # be restored when examples finish.
230
+ #
231
+ # @api private
232
+ def self.ensure_registered_with_mocks_space
233
+ return if @registered_with_mocks_space
234
+ ::RSpec::Mocks.space.add(self)
235
+ @registered_with_mocks_space = true
236
+ end
237
+
238
+ # Resets all stubbed constants. This is called automatically
239
+ # by rspec-mocks when an example finishes.
240
+ #
241
+ # @api private
242
+ def self.rspec_reset
243
+ @registered_with_mocks_space = false
244
+
245
+ # We use reverse order so that if the same constant
246
+ # was stubbed multiple times, the original value gets
247
+ # properly restored.
248
+ stubbers.reverse.each { |s| s.rspec_reset }
249
+
250
+ stubbers.clear
251
+ end
252
+
253
+ # The list of constant stubbers that have been used for
254
+ # the current example.
255
+ #
256
+ # @api private
257
+ def self.stubbers
258
+ @stubbers ||= []
259
+ end
260
+
261
+ def self.find(name)
262
+ stubbers.find { |s| s.full_constant_name == name }
263
+ end
264
+
265
+ # Used internally by the constant stubbing to raise a helpful
266
+ # error when a constant like "A::B::C" is stubbed and A::B is
267
+ # not a module (and thus, it's impossible to define "A::B::C"
268
+ # since only modules can have nested constants).
269
+ #
270
+ # @api private
271
+ def self.raise_on_invalid_const
272
+ lambda do |const_name, failed_name|
273
+ raise "Cannot stub constant #{failed_name} on #{const_name} " +
274
+ "since #{const_name} is not a module."
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
280
+
@@ -64,8 +64,10 @@ module RSpec
64
64
  end
65
65
 
66
66
  def method_missing(message, *args, &block)
67
- raise NoMethodError if message == :to_ary
67
+ raise NoMethodError if message == :to_ary
68
+ return 0 if message == :to_int && __mock_proxy.null_object?
68
69
  __mock_proxy.record_message_received(message, *args, &block)
70
+
69
71
  begin
70
72
  __mock_proxy.null_object? ? self : super
71
73
  rescue NameError
@@ -99,4 +101,3 @@ module RSpec
99
101
  end
100
102
  end
101
103
  end
102
-
@@ -1,7 +1,7 @@
1
1
  module RSpec
2
2
  module Mocks
3
3
  module Version
4
- STRING = '2.10.1'
4
+ STRING = '2.11.0'
5
5
  end
6
6
  end
7
7
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe RSpec::Mocks::AnyInstance::MessageChains do
4
4
  let(:chains) { RSpec::Mocks::AnyInstance::MessageChains.new }
5
5
  let(:stub_chain) { RSpec::Mocks::AnyInstance::StubChain.new }
6
- let(:expectation_chain) { RSpec::Mocks::AnyInstance::ExpectationChain.new }
6
+ let(:expectation_chain) { RSpec::Mocks::AnyInstance::PositiveExpectationChain.new }
7
7
 
8
8
  it "knows if a method does not have an expectation set on it" do
9
9
  chains.add(:method_name, stub_chain)
@@ -34,13 +34,13 @@ module RSpec
34
34
  lambda{ klass.any_instance.stub(:foo).and_yield(1).with("1") }.should raise_error(NoMethodError)
35
35
  end
36
36
  end
37
-
37
+
38
38
  context "#stub_chain" do
39
39
  it "raises an error if 'stub_chain' follows 'any_instance'" do
40
40
  lambda{ klass.any_instance.and_return("1").stub_chain(:foo, :bar) }.should raise_error(NoMethodError)
41
41
  end
42
42
  end
43
-
43
+
44
44
  context "#should_receive" do
45
45
  it "raises an error if 'should_receive' follows 'with'" do
46
46
  lambda{ klass.any_instance.with("1").should_receive(:foo) }.should raise_error(NoMethodError)
@@ -57,13 +57,13 @@ module RSpec
57
57
  end
58
58
  end
59
59
  end
60
-
60
+
61
61
  context "with #stub" do
62
62
  it "does not suppress an exception when a method that doesn't exist is invoked" do
63
63
  klass.any_instance.stub(:foo)
64
64
  lambda{ klass.new.bar }.should raise_error(NoMethodError)
65
65
  end
66
-
66
+
67
67
  context 'multiple methods' do
68
68
  it "allows multiple methods to be stubbed in a single invocation" do
69
69
  klass.any_instance.stub(:foo => 'foo', :bar => 'bar')
@@ -71,12 +71,12 @@ module RSpec
71
71
  instance.foo.should eq('foo')
72
72
  instance.bar.should eq('bar')
73
73
  end
74
-
74
+
75
75
  it "adheres to the contract of multiple method stubbing withou any instance" do
76
76
  Object.new.stub(:foo => 'foo', :bar => 'bar').should eq(:foo => 'foo', :bar => 'bar')
77
77
  klass.any_instance.stub(:foo => 'foo', :bar => 'bar').should eq(:foo => 'foo', :bar => 'bar')
78
78
  end
79
-
79
+
80
80
  context "allows a chain of methods to be stubbed using #stub_chain" do
81
81
  it "given symbols representing the methods" do
82
82
  klass.any_instance.stub_chain(:one, :two, :three).and_return(:four)
@@ -87,14 +87,14 @@ module RSpec
87
87
  klass.any_instance.stub_chain(:one, :two, :three => :four)
88
88
  klass.new.one.two.three.should eq(:four)
89
89
  end
90
-
90
+
91
91
  it "given a string of '.' separated method names representing the chain" do
92
92
  klass.any_instance.stub_chain('one.two.three').and_return(:four)
93
93
  klass.new.one.two.three.should eq(:four)
94
94
  end
95
95
  end
96
96
  end
97
-
97
+
98
98
  context "behaves as 'every instance'" do
99
99
  it "stubs every instance in the spec" do
100
100
  klass.any_instance.stub(:foo).and_return(result = Object.new)
@@ -108,13 +108,13 @@ module RSpec
108
108
  instance.foo.should eq(result)
109
109
  end
110
110
  end
111
-
111
+
112
112
  context "with argument matching" do
113
- before do
113
+ before do
114
114
  klass.any_instance.stub(:foo).with(:param_one, :param_two).and_return(:result_one)
115
115
  klass.any_instance.stub(:foo).with(:param_three, :param_four).and_return(:result_two)
116
116
  end
117
-
117
+
118
118
  it "returns the stubbed value when arguments match" do
119
119
  instance = klass.new
120
120
  instance.foo(:param_one, :param_two).should eq(:result_one)
@@ -127,11 +127,11 @@ module RSpec
127
127
  end.to(raise_error(RSpec::Mocks::MockExpectationError))
128
128
  end
129
129
  end
130
-
130
+
131
131
  context "with multiple stubs" do
132
- before do
133
- klass.any_instance.stub(:foo).and_return(1)
134
- klass.any_instance.stub(:bar).and_return(2)
132
+ before do
133
+ klass.any_instance.stub(:foo).and_return(1)
134
+ klass.any_instance.stub(:bar).and_return(2)
135
135
  end
136
136
 
137
137
  it "stubs a method" do
@@ -196,7 +196,7 @@ module RSpec
196
196
  klass.new.foo.should eq(klass.new.foo)
197
197
  end
198
198
  end
199
-
199
+
200
200
  context "core ruby objects" do
201
201
  it "works uniformly across *everything*" do
202
202
  Object.any_instance.stub(:foo).and_return(1)
@@ -244,7 +244,7 @@ module RSpec
244
244
  end.to raise_error(/Use stub instead/)
245
245
  end
246
246
  end
247
-
247
+
248
248
  context "unstub implementation" do
249
249
  it "replaces the stubbed method with the original method" do
250
250
  klass.any_instance.stub(:existing_method)
@@ -264,7 +264,7 @@ module RSpec
264
264
  klass.any_instance.stub(:existing_method_with_arguments).with(1)
265
265
  klass.any_instance.stub(:existing_method_with_arguments).with(2)
266
266
  klass.any_instance.unstub(:existing_method_with_arguments)
267
- klass.new.existing_method_with_arguments(3).should eq :three
267
+ klass.new.existing_method_with_arguments(3).should eq(:three)
268
268
  end
269
269
 
270
270
  it "raises a MockExpectationError if the method has not been stubbed" do
@@ -273,6 +273,37 @@ module RSpec
273
273
  end.should raise_error(RSpec::Mocks::MockExpectationError, 'The method `existing_method` was not stubbed or was already unstubbed')
274
274
  end
275
275
  end
276
+
277
+ context "with #should_not_receive" do
278
+ it "fails if the method is called" do
279
+ klass.any_instance.should_not_receive(:existing_method)
280
+ lambda { klass.new.existing_method }.should raise_error(RSpec::Mocks::MockExpectationError)
281
+ end
282
+
283
+ it "passes if no method is called" do
284
+ lambda { klass.any_instance.should_not_receive(:existing_method) }.should_not raise_error
285
+ end
286
+
287
+ it "passes if only a different method is called" do
288
+ klass.any_instance.should_not_receive(:existing_method)
289
+ lambda { klass.new.another_existing_method }.should_not raise_error
290
+ end
291
+
292
+ context "with constraints" do
293
+ it "fails if the method is called with the specified parameters" do
294
+ klass.any_instance.should_not_receive(:existing_method_with_arguments).with(:argument_one, :argument_two)
295
+ lambda do
296
+ klass.new.existing_method_with_arguments(:argument_one, :argument_two)
297
+ end.should raise_error(RSpec::Mocks::MockExpectationError)
298
+ end
299
+
300
+ it "passes if the method is called with different parameters" do
301
+ klass.any_instance.should_not_receive(:existing_method_with_arguments).with(:argument_one, :argument_two)
302
+ lambda { klass.new.existing_method_with_arguments(:argument_three, :argument_four) }.should_not raise_error
303
+ end
304
+ end
305
+ end
306
+
276
307
  context "with #should_receive" do
277
308
  let(:foo_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: foo' }
278
309
  let(:existing_method_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: existing_method' }
@@ -406,11 +437,11 @@ module RSpec
406
437
  end
407
438
 
408
439
  context "with argument matching" do
409
- before do
440
+ before do
410
441
  klass.any_instance.should_receive(:foo).with(:param_one, :param_two).and_return(:result_one)
411
442
  klass.any_instance.should_receive(:foo).with(:param_three, :param_four).and_return(:result_two)
412
443
  end
413
-
444
+
414
445
  it "returns the expected value when arguments match" do
415
446
  instance = klass.new
416
447
  instance.foo(:param_one, :param_two).should eq(:result_one)
@@ -434,7 +465,7 @@ module RSpec
434
465
  instance.foo(:param_one, :param_two).should eq(:result_one)
435
466
  instance.foo(:param_three, :param_four).should eq(:result_two)
436
467
  end
437
-
468
+
438
469
  it "fails when arguments do not match" do
439
470
  instance = klass.new
440
471
  expect do
@@ -676,21 +707,21 @@ module RSpec
676
707
  klass.new.existing_method.should eq(existing_method_return_value)
677
708
  end
678
709
  end
679
-
710
+
680
711
  context "private methods" do
681
712
  before :each do
682
713
  klass.any_instance.stub(:private_method).and_return(:something)
683
714
  space.verify_all
684
715
  end
685
-
716
+
686
717
  it "cleans up the backed up method" do
687
718
  klass.method_defined?(:__existing_method_without_any_instance__).should be_false
688
719
  end
689
-
720
+
690
721
  it "restores a stubbed private method after the spec is run" do
691
722
  klass.private_method_defined?(:private_method).should be_true
692
723
  end
693
-
724
+
694
725
  it "ensures that the restored method behaves as it originally did" do
695
726
  klass.new.send(:private_method).should eq(:private_method_return_value)
696
727
  end
@@ -717,7 +748,7 @@ module RSpec
717
748
  klass.new.send(:private_method).should eq(:private_method_return_value)
718
749
  end
719
750
  end
720
-
751
+
721
752
  context "ensures that the subsequent specs do not see expectations set in previous specs" do
722
753
  context "when the instance created after the expectation is set" do
723
754
  it "first spec" do
@@ -780,6 +811,15 @@ module RSpec
780
811
  end
781
812
  end
782
813
 
814
+ context 'when used in conjunction with a `dup`' do
815
+ it "doesn't cause an infinite loop" do
816
+ Object.any_instance.stub(:some_method)
817
+ o = Object.new
818
+ o.some_method
819
+ lambda { o.dup.some_method }.should_not raise_error(SystemStackError)
820
+ end
821
+ end
822
+
783
823
  end
784
824
  end
785
825
  end