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.
Files changed (51) hide show
  1. data/Changelog.md +16 -0
  2. data/README.md +26 -1
  3. data/features/argument_matchers/explicit.feature +2 -2
  4. data/features/argument_matchers/general_matchers.feature +4 -4
  5. data/features/argument_matchers/type_matchers.feature +1 -1
  6. data/features/message_expectations/README.md +4 -0
  7. data/features/message_expectations/any_instance.feature +1 -1
  8. data/features/message_expectations/call_original.feature +24 -0
  9. data/features/message_expectations/expect_message.feature +5 -5
  10. data/features/message_expectations/receive_counts.feature +7 -7
  11. data/features/message_expectations/warn_when_expectation_is_set_on_nil.feature +3 -3
  12. data/features/method_stubs/any_instance.feature +6 -7
  13. data/features/method_stubs/as_null_object.feature +1 -1
  14. data/features/method_stubs/simple_return_value.feature +2 -2
  15. data/features/method_stubs/stub_chain.feature +1 -1
  16. data/features/method_stubs/stub_implementation.feature +1 -1
  17. data/features/method_stubs/to_ary.feature +1 -1
  18. data/features/{stubbing_constants → mutating_constants}/README.md +21 -1
  19. data/features/mutating_constants/hiding_defined_constant.feature +64 -0
  20. data/features/{stubbing_constants → mutating_constants}/stub_defined_constant.feature +0 -0
  21. data/features/{stubbing_constants → mutating_constants}/stub_undefined_constant.feature +0 -0
  22. data/features/outside_rspec/configuration.feature +3 -3
  23. data/features/outside_rspec/standalone.feature +6 -5
  24. data/lib/rspec/mocks.rb +17 -1
  25. data/lib/rspec/mocks/any_instance.rb +12 -12
  26. data/lib/rspec/mocks/configuration.rb +28 -0
  27. data/lib/rspec/mocks/error_generator.rb +6 -0
  28. data/lib/rspec/mocks/example_methods.rb +34 -9
  29. data/lib/rspec/mocks/framework.rb +3 -2
  30. data/lib/rspec/mocks/instance_method_stasher.rb +70 -0
  31. data/lib/rspec/mocks/message_expectation.rb +49 -29
  32. data/lib/rspec/mocks/method_double.rb +84 -7
  33. data/lib/rspec/mocks/{stub_const.rb → mutate_const.rb} +117 -40
  34. data/lib/rspec/mocks/proxy.rb +16 -5
  35. data/lib/rspec/mocks/version.rb +1 -1
  36. data/spec/rspec/mocks/and_call_original_spec.rb +162 -0
  37. data/spec/rspec/mocks/any_instance_spec.rb +18 -7
  38. data/spec/rspec/mocks/configuration_spec.rb +26 -0
  39. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +9 -10
  40. data/spec/rspec/mocks/instance_method_stasher_spec.rb +58 -0
  41. data/spec/rspec/mocks/mock_spec.rb +35 -35
  42. data/spec/rspec/mocks/{stub_const_spec.rb → mutate_const_spec.rb} +142 -13
  43. data/spec/rspec/mocks/null_object_mock_spec.rb +3 -2
  44. data/spec/rspec/mocks/partial_mock_spec.rb +102 -77
  45. data/spec/rspec/mocks/serialization_spec.rb +1 -2
  46. data/spec/rspec/mocks/stub_implementation_spec.rb +6 -6
  47. data/spec/rspec/mocks_spec.rb +7 -0
  48. data/spec/spec_helper.rb +11 -0
  49. metadata +79 -80
  50. data/lib/rspec/mocks/stashed_instance_method.rb +0 -60
  51. data/spec/rspec/mocks/stashed_instance_method_spec.rb +0 -53
@@ -170,18 +170,29 @@ module RSpec
170
170
  end
171
171
 
172
172
  def find_matching_expectation(method_name, *args)
173
- (method_double[method_name].expectations.find {|expectation| expectation.matches?(method_name, *args) && !expectation.called_max_times?}) ||
174
- method_double[method_name].expectations.find {|expectation| expectation.matches?(method_name, *args)}
173
+ find_best_matching_expectation_for(method_name) do |expectation|
174
+ expectation.matches?(method_name, *args)
175
+ end
175
176
  end
176
177
 
177
178
  def find_almost_matching_expectation(method_name, *args)
178
- method_double[method_name].expectations.find do |expectation|
179
- expectation.matches_name_but_not_args(method_name, *args) && !expectation.called_max_times?
180
- end || method_double[method_name].expectations.find do |expectation|
179
+ find_best_matching_expectation_for(method_name) do |expectation|
181
180
  expectation.matches_name_but_not_args(method_name, *args)
182
181
  end
183
182
  end
184
183
 
184
+ def find_best_matching_expectation_for(method_name)
185
+ first_match = nil
186
+
187
+ method_double[method_name].expectations.each do |expectation|
188
+ next unless yield expectation
189
+ return expectation unless expectation.called_max_times?
190
+ first_match ||= expectation
191
+ end
192
+
193
+ first_match
194
+ end
195
+
185
196
  def find_matching_method_stub(method_name, *args)
186
197
  method_double[method_name].stubs.find {|stub| stub.matches?(method_name, *args)}
187
198
  end
@@ -1,7 +1,7 @@
1
1
  module RSpec
2
2
  module Mocks
3
3
  module Version
4
- STRING = '2.11.3'
4
+ STRING = '2.12.0'
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+
3
+ describe "and_call_original" do
4
+ context "on a partial mock object" do
5
+ let(:klass) do
6
+ Class.new do
7
+ def meth_1
8
+ :original
9
+ end
10
+
11
+ def meth_2(x)
12
+ yield x, :additional_yielded_arg
13
+ end
14
+
15
+ def self.new_instance
16
+ new
17
+ end
18
+ end
19
+ end
20
+
21
+ let(:instance) { klass.new }
22
+
23
+ it 'passes the received message through to the original method' do
24
+ instance.should_receive(:meth_1).and_call_original
25
+ expect(instance.meth_1).to eq(:original)
26
+ end
27
+
28
+ it 'passes args and blocks through to the original method' do
29
+ instance.should_receive(:meth_2).and_call_original
30
+ value = instance.meth_2(:submitted_arg) { |a, b| [a, b] }
31
+ expect(value).to eq([:submitted_arg, :additional_yielded_arg])
32
+ end
33
+
34
+ it 'works for singleton methods' do
35
+ def instance.foo; :bar; end
36
+ instance.should_receive(:foo).and_call_original
37
+ expect(instance.foo).to eq(:bar)
38
+ end
39
+
40
+ if RUBY_VERSION.to_f > 1.8
41
+ it 'works for class methods defined on a superclass' do
42
+ subclass = Class.new(klass)
43
+ subclass.should_receive(:new_instance).and_call_original
44
+ expect(subclass.new_instance).to be_a(subclass)
45
+ end
46
+
47
+ it 'works for class methods defined on a grandparent class' do
48
+ sub_subclass = Class.new(Class.new(klass))
49
+ sub_subclass.should_receive(:new_instance).and_call_original
50
+ expect(sub_subclass.new_instance).to be_a(sub_subclass)
51
+ end
52
+ else
53
+ it 'attempts to work for class methods defined on a superclass but ' +
54
+ 'executes the method with `self` as the superclass' do
55
+ ::Kernel.stub(:warn)
56
+ subclass = Class.new(klass)
57
+ subclass.should_receive(:new_instance).and_call_original
58
+ expect(subclass.new_instance).to be_an_instance_of(klass)
59
+ end
60
+
61
+ it 'prints a warning to notify users that `self` will not be correct' do
62
+ subclass = Class.new(klass)
63
+ ::Kernel.should_receive(:warn).with(/may not work correctly/)
64
+ subclass.should_receive(:new_instance).and_call_original
65
+ subclass.new_instance
66
+ end
67
+ end
68
+
69
+ it 'works for class methods defined on the Class class' do
70
+ klass.should_receive(:new).and_call_original
71
+ expect(klass.new).to be_an_instance_of(klass)
72
+ end
73
+
74
+ it "works for instance methods defined on the object's class's superclass" do
75
+ subclass = Class.new(klass)
76
+ inst = subclass.new
77
+ inst.should_receive(:meth_1).and_call_original
78
+ expect(inst.meth_1).to eq(:original)
79
+ end
80
+
81
+ context 'on an object that defines method_missing' do
82
+ before do
83
+ klass.class_eval do
84
+ private
85
+
86
+ def method_missing(name, *args)
87
+ if name.to_s == "greet_jack"
88
+ "Hello, jack"
89
+ else
90
+ super
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ it 'works when the method_missing definition handles the message' do
97
+ instance.should_receive(:greet_jack).and_call_original
98
+ expect(instance.greet_jack).to eq("Hello, jack")
99
+ end
100
+
101
+ it 'raises an error on invocation if method_missing does not handle the message' do
102
+ instance.should_receive(:not_a_handled_message).and_call_original
103
+
104
+ # Note: it should raise a NoMethodError (and usually does), but
105
+ # due to a weird rspec-expectations issue (see #183) it sometimes
106
+ # raises a `NameError` when a `be_xxx` predicate matcher has been
107
+ # recently used. `NameError` is the superclass of `NoMethodError`
108
+ # so this example will pass regardless.
109
+ # If/when we solve the rspec-expectations issue, this can (and should)
110
+ # be changed to `NoMethodError`.
111
+ expect {
112
+ instance.not_a_handled_message
113
+ }.to raise_error(NameError, /not_a_handled_message/)
114
+ end
115
+ end
116
+ end
117
+
118
+ context "on a partial mock object that overrides #method" do
119
+ let(:request_klass) do
120
+ Struct.new(:method, :url) do
121
+ def perform
122
+ :the_response
123
+ end
124
+
125
+ def self.method
126
+ :some_method
127
+ end
128
+ end
129
+ end
130
+
131
+ let(:request) { request_klass.new(:get, "http://foo.com/bar") }
132
+
133
+ it 'still works even though #method has been overriden' do
134
+ request.should_receive(:perform).and_call_original
135
+ expect(request.perform).to eq(:the_response)
136
+ end
137
+
138
+ it 'works for a singleton method' do
139
+ def request.perform
140
+ :a_response
141
+ end
142
+
143
+ request.should_receive(:perform).and_call_original
144
+ expect(request.perform).to eq(:a_response)
145
+ end
146
+ end
147
+
148
+ context "on a pure mock object" do
149
+ let(:instance) { double }
150
+
151
+ it 'raises an error even if the mock object responds to the message' do
152
+ expect(instance.to_s).to be_a(String)
153
+ mock_expectation = instance.should_receive(:to_s)
154
+ instance.to_s # to satisfy the expectation
155
+
156
+ expect {
157
+ mock_expectation.and_call_original
158
+ }.to raise_error(/and_call_original.*partial mock/i)
159
+ end
160
+ end
161
+ end
162
+
@@ -273,37 +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
-
276
+
277
277
  context "with #should_not_receive" do
278
278
  it "fails if the method is called" do
279
279
  klass.any_instance.should_not_receive(:existing_method)
280
280
  lambda { klass.new.existing_method }.should raise_error(RSpec::Mocks::MockExpectationError)
281
281
  end
282
-
282
+
283
283
  it "passes if no method is called" do
284
284
  lambda { klass.any_instance.should_not_receive(:existing_method) }.should_not raise_error
285
285
  end
286
-
286
+
287
287
  it "passes if only a different method is called" do
288
288
  klass.any_instance.should_not_receive(:existing_method)
289
289
  lambda { klass.new.another_existing_method }.should_not raise_error
290
290
  end
291
-
291
+
292
292
  context "with constraints" do
293
293
  it "fails if the method is called with the specified parameters" do
294
294
  klass.any_instance.should_not_receive(:existing_method_with_arguments).with(:argument_one, :argument_two)
295
295
  lambda do
296
- klass.new.existing_method_with_arguments(:argument_one, :argument_two)
296
+ klass.new.existing_method_with_arguments(:argument_one, :argument_two)
297
297
  end.should raise_error(RSpec::Mocks::MockExpectationError)
298
298
  end
299
-
299
+
300
300
  it "passes if the method is called with different parameters" do
301
301
  klass.any_instance.should_not_receive(:existing_method_with_arguments).with(:argument_one, :argument_two)
302
302
  lambda { klass.new.existing_method_with_arguments(:argument_three, :argument_four) }.should_not raise_error
303
303
  end
304
304
  end
305
305
  end
306
-
306
+
307
307
  context "with #should_receive" do
308
308
  let(:foo_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: foo' }
309
309
  let(:existing_method_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: existing_method' }
@@ -825,6 +825,17 @@ module RSpec
825
825
  end
826
826
  klass.any_instance
827
827
  end
828
+
829
+ it "doesn't fail when dup accepts parameters" do
830
+ klass = Class.new do
831
+ def dup(funky_option)
832
+ end
833
+ end
834
+
835
+ klass.any_instance
836
+
837
+ lambda { klass.new.dup('Dup dup dup') }.should_not raise_error(ArgumentError)
838
+ end
828
839
  end
829
840
 
830
841
  context "when directed at a method defined on a superclass" do
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module RSpec
4
+ module Mocks
5
+ describe Configuration do
6
+ let(:config) { Configuration.new }
7
+ let(:mod_1) { Module.new }
8
+ let(:mod_2) { Module.new }
9
+
10
+ def instance_methods_of(mod)
11
+ mod_1.instance_methods.map(&:to_sym)
12
+ end
13
+
14
+ it 'adds stub and should_receive to the given modules' do
15
+ instance_methods_of(mod_1).should_not include(:stub, :should_receive)
16
+ instance_methods_of(mod_2).should_not include(:stub, :should_receive)
17
+
18
+ config.add_stub_and_should_receive_to(mod_1, mod_2)
19
+
20
+ instance_methods_of(mod_1).should include(:stub, :should_receive)
21
+ instance_methods_of(mod_2).should include(:stub, :should_receive)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -7,7 +7,7 @@ module RSpec
7
7
  @double = double("double")
8
8
  @reporter = double("reporter").as_null_object
9
9
  end
10
-
10
+
11
11
  after(:each) do
12
12
  @double.rspec_reset
13
13
  end
@@ -25,14 +25,14 @@ module RSpec
25
25
  @double.random_call("1")
26
26
  end.to raise_error(RSpec::Mocks::MockExpectationError)
27
27
  end
28
-
28
+
29
29
  it "rejects non string" do
30
30
  @double.should_receive(:random_call).with(an_instance_of(String))
31
31
  expect do
32
32
  @double.random_call(123)
33
33
  end.to raise_error(RSpec::Mocks::MockExpectationError)
34
34
  end
35
-
35
+
36
36
  it "rejects goose when expecting a duck" do
37
37
  @double.should_receive(:random_call).with(duck_type(:abs, :div))
38
38
  expect { @double.random_call("I don't respond to :abs or :div") }.to raise_error(RSpec::Mocks::MockExpectationError)
@@ -42,40 +42,40 @@ module RSpec
42
42
  @double.should_receive(:random_call).with(/bcd/)
43
43
  expect { @double.random_call("abc") }.to raise_error(RSpec::Mocks::MockExpectationError)
44
44
  end
45
-
45
+
46
46
  it "fails if regexp does not match submitted regexp" do
47
47
  @double.should_receive(:random_call).with(/bcd/)
48
48
  expect { @double.random_call(/bcde/) }.to raise_error(RSpec::Mocks::MockExpectationError)
49
49
  end
50
-
50
+
51
51
  it "fails for a hash w/ wrong values" do
52
52
  @double.should_receive(:random_call).with(:a => "b", :c => "d")
53
53
  expect do
54
54
  @double.random_call(:a => "b", :c => "e")
55
55
  end.to raise_error(RSpec::Mocks::MockExpectationError, /Double "double" received :random_call with unexpected arguments\n expected: \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\)\n got: \(\{(:a=>\"b\", :c=>\"e\"|:c=>\"e\", :a=>\"b\")\}\)/)
56
56
  end
57
-
57
+
58
58
  it "fails for a hash w/ wrong keys" do
59
59
  @double.should_receive(:random_call).with(:a => "b", :c => "d")
60
60
  expect do
61
61
  @double.random_call("a" => "b", "c" => "d")
62
62
  end.to raise_error(RSpec::Mocks::MockExpectationError, /Double "double" received :random_call with unexpected arguments\n expected: \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\)\n got: \(\{(\"a\"=>\"b\", \"c\"=>\"d\"|\"c\"=>\"d\", \"a\"=>\"b\")\}\)/)
63
63
  end
64
-
64
+
65
65
  it "matches against a Matcher" do
66
66
  expect do
67
67
  @double.should_receive(:msg).with(equal(3))
68
68
  @double.msg(37)
69
69
  end.to raise_error(RSpec::Mocks::MockExpectationError, "Double \"double\" received :msg with unexpected arguments\n expected: (equal 3)\n got: (37)")
70
70
  end
71
-
71
+
72
72
  it "fails no_args with one arg" do
73
73
  expect do
74
74
  @double.should_receive(:msg).with(no_args)
75
75
  @double.msg(37)
76
76
  end.to raise_error(RSpec::Mocks::MockExpectationError, "Double \"double\" received :msg with unexpected arguments\n expected: (no args)\n got: (37)")
77
77
  end
78
-
78
+
79
79
  it "fails hash_including with missing key" do
80
80
  expect do
81
81
  @double.should_receive(:msg).with(hash_including(:a => 1))
@@ -89,7 +89,6 @@ module RSpec
89
89
  @double.msg :no_msg_for_you
90
90
  end.to raise_error(RSpec::Expectations::ExpectationNotMetError, /expected: :received.*\s*.*got: :no_msg_for_you/)
91
91
  end
92
-
93
92
  end
94
93
  end
95
94
  end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ module RSpec
4
+ module Mocks
5
+ describe InstanceMethodStasher do
6
+ class ExampleClass
7
+ def hello
8
+ :hello_defined_on_class
9
+ end
10
+ end
11
+
12
+ def singleton_class_for(obj)
13
+ class << obj; self; end
14
+ end
15
+
16
+ it "stashes the current implementation of an instance method so it can be temporarily replaced" do
17
+ obj = Object.new
18
+ def obj.hello; :hello_defined_on_singleton_class; end;
19
+
20
+ stashed_method = InstanceMethodStasher.new(singleton_class_for(obj), :hello)
21
+ stashed_method.stash
22
+
23
+ def obj.hello; :overridden_hello; end
24
+ expect(obj.hello).to eql :overridden_hello
25
+
26
+ stashed_method.restore
27
+ expect(obj.hello).to eql :hello_defined_on_singleton_class
28
+ end
29
+
30
+ it "stashes private instance methods" do
31
+ obj = Object.new
32
+ def obj.hello; :hello_defined_on_singleton_class; end;
33
+ singleton_class_for(obj).__send__(:private, :hello)
34
+
35
+ stashed_method = InstanceMethodStasher.new(singleton_class_for(obj), :hello)
36
+ stashed_method.stash
37
+
38
+ def obj.hello; :overridden_hello; end
39
+ stashed_method.restore
40
+ expect(obj.send(:hello)).to eql :hello_defined_on_singleton_class
41
+ end
42
+
43
+ it "only stashes methods directly defined on the given class, not its ancestors" do
44
+ obj = ExampleClass.new
45
+
46
+ stashed_method = InstanceMethodStasher.new(singleton_class_for(obj), :hello)
47
+ stashed_method.stash
48
+
49
+ def obj.hello; :overridden_hello; end;
50
+ expect(obj.hello).to eql :overridden_hello
51
+
52
+ stashed_method.restore
53
+ expect(obj.hello).to eql :overridden_hello
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -42,11 +42,11 @@ module RSpec
42
42
  @double.rspec_verify
43
43
  end
44
44
 
45
- it "passes when not receiving message specified as not to be received with and_return" do
46
- # NOTE (DC 2012-05-05) calling `and_return` after `should_not_receive` makes no sense
47
- # and should probably be disallowed.
48
- @double.should_not_receive(:not_expected).and_return nil
49
- @double.rspec_verify
45
+ it "warns when should_not_receive is followed by and_return" do
46
+ RSpec.should_receive(:warn_deprecation).
47
+ with(/`and_return` with `should_not_receive` is deprecated/)
48
+
49
+ @double.should_not_receive(:do_something).and_return(1)
50
50
  end
51
51
 
52
52
  it "passes when receiving message specified as not to be received with different args" do
@@ -214,49 +214,49 @@ module RSpec
214
214
  )
215
215
  end
216
216
 
217
- it "raises when told to" do
218
- @double.should_receive(:something).and_raise(StandardError)
219
- expect { @double.something }.to raise_error(StandardError)
220
- end
221
-
222
217
  it "raises RuntimeError by default" do
223
218
  @double.should_receive(:something).and_raise
224
219
  expect { @double.something }.to raise_error(RuntimeError)
225
220
  end
226
221
 
227
- it "raises instance of submitted Exception" do
228
- error = RuntimeError.new("error message")
229
- @double.should_receive(:something).and_raise(error)
230
- lambda {
231
- @double.something
232
- }.should raise_error(RuntimeError, "error message")
222
+ it "raises RuntimeError with a message by default" do
223
+ @double.should_receive(:something).and_raise("error message")
224
+ expect { @double.something }.to raise_error(RuntimeError, "error message")
233
225
  end
234
226
 
235
- it "raises instance of submitted ArgumentError" do
236
- error = ArgumentError.new("error message")
237
- @double.should_receive(:something).and_raise(error)
238
- lambda {
239
- @double.something
240
- }.should raise_error(ArgumentError, "error message")
227
+ it "raises an exception of a given type without an error message" do
228
+ @double.should_receive(:something).and_raise(StandardError)
229
+ expect { @double.something }.to raise_error(StandardError)
241
230
  end
242
231
 
243
- it "fails with helpful message if submitted Exception requires constructor arguments" do
244
- class ErrorWithNonZeroArgConstructor < RuntimeError
245
- def initialize(i_take_an_argument)
246
- end
247
- end
232
+ it "raises an exception of a given type with a message" do
233
+ @double.should_receive(:something).and_raise(RuntimeError, "error message")
234
+ expect { @double.something }.to raise_error(RuntimeError, "error message")
235
+ end
248
236
 
249
- @double.stub(:something).and_raise(ErrorWithNonZeroArgConstructor)
250
- lambda {
251
- @double.something
252
- }.should raise_error(ArgumentError, /^'and_raise' can only accept an Exception class if an instance/)
237
+ it "raises a given instance of an exception" do
238
+ @double.should_receive(:something).and_raise(RuntimeError.new("error message"))
239
+ expect { @double.something }.to raise_error(RuntimeError, "error message")
253
240
  end
254
241
 
255
- it "raises RuntimeError with submitted message" do
256
- @double.should_receive(:something).and_raise("error message")
257
- lambda {
242
+ class OutOfGas < StandardError
243
+ attr_reader :amount, :units
244
+ def initialize(amount, units)
245
+ @amount = amount
246
+ @units = units
247
+ end
248
+ end
249
+
250
+ it "raises a given instance of an exception with arguments other than the standard 'message'" do
251
+ @double.should_receive(:something).and_raise(OutOfGas.new(2, :oz))
252
+
253
+ begin
258
254
  @double.something
259
- }.should raise_error(RuntimeError, "error message")
255
+ fail "OutOfGas was not raised"
256
+ rescue OutOfGas => e
257
+ e.amount.should == 2
258
+ e.units.should == :oz
259
+ end
260
260
  end
261
261
 
262
262
  it "does not raise when told to if args dont match" do