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.
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