rspec-mocks 3.0.0.beta1 → 3.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. data.tar.gz.sig +1 -1
  2. data/Changelog.md +95 -3
  3. data/README.md +27 -13
  4. data/features/README.md +15 -7
  5. data/features/argument_matchers/README.md +5 -5
  6. data/features/argument_matchers/explicit.feature +6 -6
  7. data/features/argument_matchers/general_matchers.feature +4 -4
  8. data/features/argument_matchers/type_matchers.feature +2 -2
  9. data/features/message_expectations/README.md +29 -27
  10. data/features/message_expectations/call_original.feature +0 -1
  11. data/features/message_expectations/message_chains_using_expect.feature +49 -0
  12. data/features/method_stubs/README.md +2 -1
  13. data/features/method_stubs/{any_instance.feature → allow_any_instance_of.feature} +12 -12
  14. data/features/method_stubs/{stub_chain.feature → receive_message_chain.feature} +3 -3
  15. data/features/method_stubs/to_ary.feature +1 -1
  16. data/features/mutating_constants/stub_defined_constant.feature +0 -1
  17. data/features/outside_rspec/standalone.feature +1 -1
  18. data/features/spies/spy_pure_mock_method.feature +1 -1
  19. data/features/test_frameworks/test_unit.feature +21 -10
  20. data/features/verifying_doubles/README.md +17 -0
  21. data/features/verifying_doubles/class_doubles.feature +1 -16
  22. data/features/verifying_doubles/dynamic_classes.feature +0 -1
  23. data/features/verifying_doubles/{introduction.feature → instance_doubles.feature} +41 -23
  24. data/features/verifying_doubles/partial_doubles.feature +2 -2
  25. data/lib/rspec/mocks.rb +69 -82
  26. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +35 -0
  27. data/lib/rspec/mocks/any_instance/expectation_chain.rb +1 -2
  28. data/lib/rspec/mocks/any_instance/recorder.rb +52 -18
  29. data/lib/rspec/mocks/any_instance/stub_chain.rb +1 -1
  30. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +4 -0
  31. data/lib/rspec/mocks/argument_list_matcher.rb +10 -44
  32. data/lib/rspec/mocks/argument_matchers.rb +132 -163
  33. data/lib/rspec/mocks/configuration.rb +28 -4
  34. data/lib/rspec/mocks/error_generator.rb +46 -13
  35. data/lib/rspec/mocks/example_methods.rb +13 -12
  36. data/lib/rspec/mocks/extensions/marshal.rb +1 -1
  37. data/lib/rspec/mocks/framework.rb +3 -4
  38. data/lib/rspec/mocks/instance_method_stasher.rb +2 -3
  39. data/lib/rspec/mocks/matchers/have_received.rb +8 -6
  40. data/lib/rspec/mocks/matchers/receive.rb +28 -20
  41. data/lib/rspec/mocks/matchers/receive_message_chain.rb +65 -0
  42. data/lib/rspec/mocks/matchers/receive_messages.rb +3 -2
  43. data/lib/rspec/mocks/message_chain.rb +91 -0
  44. data/lib/rspec/mocks/message_expectation.rb +86 -80
  45. data/lib/rspec/mocks/method_double.rb +2 -11
  46. data/lib/rspec/mocks/method_reference.rb +82 -23
  47. data/lib/rspec/mocks/method_signature_verifier.rb +207 -0
  48. data/lib/rspec/mocks/mutate_const.rb +34 -50
  49. data/lib/rspec/mocks/object_reference.rb +0 -1
  50. data/lib/rspec/mocks/proxy.rb +70 -13
  51. data/lib/rspec/mocks/ruby_features.rb +24 -0
  52. data/lib/rspec/mocks/space.rb +105 -31
  53. data/lib/rspec/mocks/standalone.rb +2 -2
  54. data/lib/rspec/mocks/syntax.rb +43 -8
  55. data/lib/rspec/mocks/targets.rb +16 -7
  56. data/lib/rspec/mocks/test_double.rb +41 -15
  57. data/lib/rspec/mocks/verifying_double.rb +51 -4
  58. data/lib/rspec/mocks/verifying_message_expecation.rb +12 -12
  59. data/lib/rspec/mocks/verifying_proxy.rb +32 -19
  60. data/lib/rspec/mocks/version.rb +1 -1
  61. data/spec/rspec/mocks/and_call_original_spec.rb +28 -7
  62. data/spec/rspec/mocks/and_return_spec.rb +23 -0
  63. data/spec/rspec/mocks/and_yield_spec.rb +1 -2
  64. data/spec/rspec/mocks/any_instance_spec.rb +33 -17
  65. data/spec/rspec/mocks/array_including_matcher_spec.rb +6 -6
  66. data/spec/rspec/mocks/before_all_spec.rb +132 -0
  67. data/spec/rspec/mocks/block_return_value_spec.rb +12 -1
  68. data/spec/rspec/mocks/combining_implementation_instructions_spec.rb +9 -11
  69. data/spec/rspec/mocks/configuration_spec.rb +14 -1
  70. data/spec/rspec/mocks/double_spec.rb +867 -24
  71. data/spec/rspec/mocks/example_methods_spec.rb +13 -0
  72. data/spec/rspec/mocks/extensions/marshal_spec.rb +17 -17
  73. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +29 -1
  74. data/spec/rspec/mocks/hash_excluding_matcher_spec.rb +12 -12
  75. data/spec/rspec/mocks/hash_including_matcher_spec.rb +21 -17
  76. data/spec/rspec/mocks/instance_method_stasher_spec.rb +2 -3
  77. data/spec/rspec/mocks/matchers/have_received_spec.rb +7 -0
  78. data/spec/rspec/mocks/matchers/receive_message_chain_spec.rb +198 -0
  79. data/spec/rspec/mocks/matchers/receive_messages_spec.rb +2 -2
  80. data/spec/rspec/mocks/matchers/receive_spec.rb +19 -6
  81. data/spec/rspec/mocks/method_signature_verifier_spec.rb +272 -0
  82. data/spec/rspec/mocks/methods_spec.rb +0 -1
  83. data/spec/rspec/mocks/multiple_return_value_spec.rb +2 -2
  84. data/spec/rspec/mocks/mutate_const_spec.rb +24 -1
  85. data/spec/rspec/mocks/nil_expectation_warning_spec.rb +6 -22
  86. data/spec/rspec/mocks/null_object_mock_spec.rb +13 -7
  87. data/spec/rspec/mocks/options_hash_spec.rb +3 -3
  88. data/spec/rspec/mocks/{partial_mock_spec.rb → partial_double_spec.rb} +5 -2
  89. data/spec/rspec/mocks/{partial_mock_using_mocks_directly_spec.rb → partial_double_using_mocks_directly_spec.rb} +1 -1
  90. data/spec/rspec/mocks/passing_argument_matchers_spec.rb +18 -0
  91. data/spec/rspec/mocks/serialization_spec.rb +1 -0
  92. data/spec/rspec/mocks/space_spec.rb +218 -7
  93. data/spec/rspec/mocks/stub_chain_spec.rb +6 -0
  94. data/spec/rspec/mocks/stub_spec.rb +0 -6
  95. data/spec/rspec/mocks/syntax_spec.rb +19 -0
  96. data/spec/rspec/mocks/test_double_spec.rb +0 -1
  97. data/spec/rspec/mocks/verifying_double_spec.rb +281 -18
  98. data/spec/rspec/mocks/verifying_message_expecation_spec.rb +7 -6
  99. data/spec/rspec/mocks_spec.rb +168 -42
  100. data/spec/spec_helper.rb +34 -22
  101. metadata +94 -63
  102. metadata.gz.sig +0 -0
  103. checksums.yaml +0 -15
  104. checksums.yaml.gz.sig +0 -2
  105. data/features/outside_rspec/configuration.feature +0 -60
  106. data/lib/rspec/mocks/arity_calculator.rb +0 -66
  107. data/lib/rspec/mocks/errors.rb +0 -12
  108. data/lib/rspec/mocks/mock.rb +0 -7
  109. data/lib/rspec/mocks/proxy_for_nil.rb +0 -37
  110. data/lib/rspec/mocks/stub_chain.rb +0 -51
  111. data/spec/rspec/mocks/argument_expectation_spec.rb +0 -32
  112. data/spec/rspec/mocks/arity_calculator_spec.rb +0 -95
  113. data/spec/rspec/mocks/mock_space_spec.rb +0 -113
  114. data/spec/rspec/mocks/mock_spec.rb +0 -788
@@ -69,6 +69,16 @@ module RSpec
69
69
  verify @double
70
70
  end
71
71
 
72
+ it 'allows unexpected message sends using `send`' do
73
+ val = @double.send(:foo).send(:bar)
74
+ expect(val).to equal(@double)
75
+ end
76
+
77
+ it 'allows unexpected message sends using `send`' do
78
+ val = @double.__send__(:foo).__send__(:bar)
79
+ expect(val).to equal(@double)
80
+ end
81
+
72
82
  it "allows expected message with different args first" do
73
83
  @double.should_receive(:message).with(:expected_arg)
74
84
  @double.message(:unexpected_arg)
@@ -89,15 +99,11 @@ module RSpec
89
99
  expect(("%i" % @double)).to eq("0")
90
100
  end
91
101
 
92
- it "does not allow null-ness to persist between examples" do
102
+ it "does not allow null objects to be used outside of examples" do
93
103
  RSpec::Mocks.teardown
94
104
 
95
- expect(@double).not_to be_null_object
96
- expect { @double.some.long.message.chain }.to raise_error(RSpec::Mocks::MockExpectationError)
97
-
98
- @double.as_null_object
99
- expect(@double).to be_null_object
100
- expect { @double.some.long.message.chain }.not_to raise_error
105
+ expect { @double.some.long.message.chain }.to raise_error(RSpec::Mocks::OutsideOfExampleError)
106
+ expect { @double.as_null_object }.to raise_error(RSpec::Mocks::OutsideOfExampleError)
101
107
  end
102
108
  end
103
109
 
@@ -5,7 +5,7 @@ module RSpec
5
5
  describe "calling :should_receive with an options hash" do
6
6
  it "reports the file and line submitted with :expected_from" do
7
7
  begin
8
- mock = RSpec::Mocks::Mock.new("a mock")
8
+ mock = RSpec::Mocks::Double.new("a mock")
9
9
  mock.should_receive(:message, :expected_from => "/path/to/blah.ext:37")
10
10
  verify mock
11
11
  rescue Exception => e
@@ -16,7 +16,7 @@ module RSpec
16
16
 
17
17
  it "uses the message supplied with :message" do
18
18
  expect {
19
- m = RSpec::Mocks::Mock.new("a mock")
19
+ m = RSpec::Mocks::Double.new("a mock")
20
20
  m.should_receive(:message, :message => "recebi nada")
21
21
  verify m
22
22
  }.to raise_error("recebi nada")
@@ -24,7 +24,7 @@ module RSpec
24
24
 
25
25
  it "uses the message supplied with :message after a similar stub" do
26
26
  expect {
27
- m = RSpec::Mocks::Mock.new("a mock")
27
+ m = RSpec::Mocks::Double.new("a mock")
28
28
  m.stub(:message)
29
29
  m.should_receive(:message, :message => "from mock")
30
30
  verify m
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  module RSpec
4
4
  module Mocks
5
- describe "using a Partial Mock," do
5
+ describe "A partial double" do
6
6
  let(:object) { Object.new }
7
7
 
8
8
  it "names the class in the failure message" do
@@ -298,7 +298,10 @@ module RSpec
298
298
  allow(object).to receive(:implemented)
299
299
  expect {
300
300
  object.implemented('bogus')
301
- }.to raise_error(ArgumentError, /wrong number of arguments \(1 for 0\)/)
301
+ }.to raise_error(
302
+ ArgumentError,
303
+ a_string_including("Wrong number of arguments. Expected 0, got 1.")
304
+ )
302
305
  end
303
306
  end
304
307
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module RSpec::Mocks
4
- describe "PartialMockUsingMocksDirectly" do
4
+ describe "PartialDoubleUsingMocksDirectly" do
5
5
  let(:klass) do
6
6
  Class.new do
7
7
  module MethodMissing
@@ -87,6 +87,14 @@ module RSpec
87
87
  @double.should_receive(:random_call).with(array_including(1,2))
88
88
  @double.random_call([1,2])
89
89
  end
90
+
91
+ it "matches any arbitrary object using #===" do
92
+ matcher = double
93
+ matcher.should_receive(:===).with(4).and_return(true)
94
+
95
+ @double.should_receive(:foo).with(matcher)
96
+ @double.foo(4)
97
+ end
90
98
  end
91
99
 
92
100
  context "handling non-matcher arguments" do
@@ -121,6 +129,16 @@ module RSpec
121
129
  @double.should_receive(:random_call).with(:a => "a", :b => "b")
122
130
  @double.random_call(opts)
123
131
  end
132
+
133
+ it "matches a class against itself" do
134
+ @double.should_receive(:foo).with(Fixnum)
135
+ @double.foo(Fixnum)
136
+ end
137
+
138
+ it "matches a class against an instance of itself" do
139
+ @double.should_receive(:foo).with(Fixnum)
140
+ @double.foo(3)
141
+ end
124
142
  end
125
143
  end
126
144
  end
@@ -61,6 +61,7 @@ module RSpec
61
61
  if compiled_with_psych
62
62
  context 'using Syck as the YAML engine' do
63
63
  before(:each) { ::YAML::ENGINE.yamler = 'syck' }
64
+ around(:each) { |example| with_isolated_stderr(&example) }
64
65
  it_behaves_like 'normal YAML serialization'
65
66
  end
66
67
 
@@ -2,16 +2,62 @@ require 'spec_helper'
2
2
 
3
3
  module RSpec::Mocks
4
4
  describe Space do
5
+ let(:space) { Space.new }
6
+ let(:dbl_1) { Object.new }
7
+ let(:dbl_2) { Object.new }
5
8
 
6
- describe "#proxies_of(klass)" do
7
- let(:space) { Space.new }
9
+ describe "#verify_all" do
10
+ it "verifies all mocks within" do
11
+ verifies = []
12
+
13
+ space.proxy_for(dbl_1).stub(:verify) { verifies << :dbl_1 }
14
+ space.proxy_for(dbl_2).stub(:verify) { verifies << :dbl_2 }
15
+
16
+ space.verify_all
17
+
18
+ expect(verifies).to match_array([:dbl_1, :dbl_2])
19
+ end
20
+
21
+ def define_singleton_method_on_recorder_for(klass, name, &block)
22
+ recorder = space.any_instance_recorder_for(klass)
23
+ (class << recorder; self; end).send(:define_method, name, &block)
24
+ end
25
+
26
+ it 'verifies all any_instance recorders within' do
27
+ klass_1, klass_2 = Class.new, Class.new
28
+
29
+ verifies = []
30
+
31
+ # We can't `stub` a method on the recorder because it defines its own `stub`...
32
+ define_singleton_method_on_recorder_for(klass_1, :verify) { verifies << :klass_1 }
33
+ define_singleton_method_on_recorder_for(klass_2, :verify) { verifies << :klass_2 }
34
+
35
+ space.verify_all
36
+
37
+ expect(verifies).to match_array([:klass_1, :klass_2])
38
+ end
39
+ end
40
+
41
+ describe "#reset_all" do
42
+ it "resets all mocks within" do
43
+ resets = []
44
+
45
+ space.proxy_for(dbl_1).stub(:reset) { resets << :dbl_1 }
46
+ space.proxy_for(dbl_2).stub(:reset) { resets << :dbl_2 }
47
+
48
+ space.reset_all
49
+
50
+ expect(resets).to match_array([:dbl_1, :dbl_2])
51
+ end
52
+ end
8
53
 
54
+ describe "#proxies_of(klass)" do
9
55
  it 'returns proxies' do
10
56
  space.proxy_for("")
11
- expect(space.proxies_of(String).map(&:class)).to eq([PartialMockProxy])
57
+ expect(space.proxies_of(String).map(&:class)).to eq([PartialDoubleProxy])
12
58
  end
13
59
 
14
- it 'returns only the proxies whose object is an instance of the given class' do
60
+ def create_generations
15
61
  grandparent_class = Class.new
16
62
  parent_class = Class.new(grandparent_class)
17
63
  child_class = Class.new(parent_class)
@@ -20,14 +66,179 @@ module RSpec::Mocks
20
66
  parent = parent_class.new
21
67
  child = child_class.new
22
68
 
69
+ return grandparent, parent, child
70
+ end
71
+
72
+ it 'returns only the proxies whose object is an instance of the given class' do
73
+ grandparent, parent, child = create_generations
74
+
23
75
  space.proxy_for(grandparent)
24
76
 
25
- parent_proxy = space.proxy_for(parent)
26
- child_proxy = space.proxy_for(child)
77
+ parent_proxy = space.proxy_for(parent)
78
+ child_proxy = space.proxy_for(child)
79
+
80
+ expect(space.proxies_of(parent.class)).to contain_exactly(parent_proxy, child_proxy)
81
+ end
82
+
83
+ it 'looks in the parent space for matching proxies' do
84
+ _, parent, child = create_generations
85
+
86
+ parent_proxy = space.proxy_for(parent)
87
+ subspace = space.new_scope
88
+ child_proxy = subspace.proxy_for(child)
89
+
90
+ expect(subspace.proxies_of(parent.class)).to contain_exactly(parent_proxy, child_proxy)
91
+ end
92
+ end
93
+
94
+ it 'tracks proxies in parent and child space separately' do
95
+ proxy1 = space.proxy_for(Object.new)
96
+ subspace = space.new_scope
97
+ proxy2 = subspace.proxy_for(Object.new)
98
+
99
+ expect(space.proxies.values).to include(proxy1)
100
+ expect(space.proxies.values).not_to include(proxy2)
101
+
102
+ expect(subspace.proxies.values).to include(proxy2)
103
+ expect(subspace.proxies.values).not_to include(proxy1)
104
+ end
105
+
106
+ it "only adds an instance once" do
107
+ m1 = double("mock1")
108
+
109
+ expect {
110
+ space.ensure_registered(m1)
111
+ }.to change { space.proxies }
112
+
113
+ expect {
114
+ space.ensure_registered(m1)
115
+ }.not_to change { space.proxies }
116
+ end
117
+
118
+ [:ensure_registered, :proxy_for].each do |method|
119
+ describe "##{method}" do
120
+ define_method :get_proxy do |space, object|
121
+ space.__send__(method, object)
122
+ end
123
+
124
+ it 'returns the proxy for the given object' do
125
+ obj1 = Object.new
126
+ obj2 = Object.new
127
+
128
+ expect(get_proxy(space, obj1)).to equal(get_proxy(space, obj1))
129
+ expect(get_proxy(space, obj2)).to equal(get_proxy(space, obj2))
130
+ expect(get_proxy(space, obj1)).not_to equal(get_proxy(space, obj2))
131
+ end
132
+
133
+ it 'can stil return a proxy from a parent context' do
134
+ proxy = get_proxy(space, Object)
135
+ subspace = space.new_scope
136
+
137
+ expect(get_proxy(subspace, Object)).to equal(proxy)
138
+ end
139
+
140
+ it "does not store a parent's proxy in the child space" do
141
+ get_proxy(space, Object)
142
+ subspace = space.new_scope
143
+
144
+ expect {
145
+ get_proxy(subspace, Object)
146
+ }.not_to change { subspace.proxies }.from({})
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "#registered?" do
152
+ it 'returns true if registered in this space' do
153
+ space.ensure_registered(Object)
154
+ expect(space).to be_registered(Object)
155
+ end
156
+
157
+ it 'returns true if registered in a parent space' do
158
+ space.ensure_registered(Object)
159
+ expect(space.new_scope).to be_registered(Object)
160
+ end
161
+
162
+ it 'returns false if not registered in this or a parent space' do
163
+ expect(space.new_scope).not_to be_registered(Object)
164
+ end
165
+ end
166
+
167
+ describe "#constant_mutator_for" do
168
+ it 'returns the mutator for the given const name' do
169
+ space = RSpec::Mocks.space
170
+ stub_const("Foo", 3)
171
+ stub_const("Bar", 4)
172
+
173
+ expect(space.constant_mutator_for("Foo")).to equal(space.constant_mutator_for("Foo"))
174
+ expect(space.constant_mutator_for("Bar")).to equal(space.constant_mutator_for("Bar"))
175
+ expect(space.constant_mutator_for("Foo")).not_to equal(space.constant_mutator_for("Bar"))
176
+ end
27
177
 
28
- expect(space.proxies_of(parent_class)).to match_array([parent_proxy, child_proxy])
178
+ it 'can stil return a mutator from a parent context' do
179
+ space = RSpec::Mocks.space
180
+
181
+ stub_const("Foo", 3)
182
+ mutator = space.constant_mutator_for("Foo")
183
+
184
+ in_new_space_scope do
185
+ subspace = RSpec::Mocks.space
186
+ expect(subspace.constant_mutator_for("Foo")).to equal(mutator)
187
+ end
188
+ end
189
+ end
190
+
191
+ describe "#any_instance_recorder_for" do
192
+ it 'returns the recorder for the given class' do
193
+ expect(space.any_instance_recorder_for(String)).to equal(space.any_instance_recorder_for(String))
194
+ expect(space.any_instance_recorder_for(Symbol)).to equal(space.any_instance_recorder_for(Symbol))
195
+ expect(space.any_instance_recorder_for(String)).not_to equal(space.any_instance_recorder_for(Symbol))
196
+ end
197
+
198
+ it 'can stil return a recorder from a parent context' do
199
+ recorder = space.any_instance_recorder_for(String)
200
+ subspace = space.new_scope
201
+
202
+ expect(subspace.any_instance_recorder_for(String)).to equal(recorder)
203
+ end
204
+
205
+ it "does not store a parent's proxy in the child space" do
206
+ space.any_instance_recorder_for(String)
207
+ subspace = space.new_scope
208
+
209
+ expect {
210
+ subspace.any_instance_recorder_for(String)
211
+ }.not_to change { subspace.any_instance_recorders }.from({})
29
212
  end
30
213
  end
31
214
 
215
+ it 'can be diffed in a failure when it has references to an error generator via a proxy' do
216
+ space1 = Space.new
217
+ space2 = Space.new
218
+
219
+ space1.proxy_for("")
220
+ space2.proxy_for("")
221
+
222
+ expect {
223
+ expect(space1).to eq(space2)
224
+ }.to raise_error(RSpec::Expectations::ExpectationNotMetError, /Diff/)
225
+ end
226
+
227
+ it 'removes an any_instance_recorder when requested' do
228
+ klass = Class.new
229
+
230
+ space.any_instance_recorder_for(klass)
231
+
232
+ expect {
233
+ space.remove_any_instance_recorder_for(klass)
234
+ }.to change { space.any_instance_recorders.size }.by(-1)
235
+ end
236
+
237
+ def in_new_space_scope
238
+ RSpec::Mocks.setup
239
+ yield
240
+ ensure
241
+ RSpec::Mocks.teardown
242
+ end
32
243
  end
33
244
  end
@@ -37,6 +37,12 @@ module RSpec
37
37
  end
38
38
 
39
39
  context "with two methods in chain" do
40
+ it "accepts any number of arguments to the stubbed messages in the chain" do
41
+ object.stub_chain(:msg1, :msg2).and_return(:return_value)
42
+
43
+ expect(object.msg1("nonsense", :value).msg2("another", :nonsense, 3.0, "value")).to eq(:return_value)
44
+ end
45
+
40
46
  context "using and_return" do
41
47
  it "returns expected value from chaining two method calls" do
42
48
  object.stub_chain(:msg1, :msg2).and_return(:return_value)
@@ -263,12 +263,6 @@ module RSpec
263
263
  @stub.foo("bar")
264
264
  @stub.foo("baz")
265
265
  end
266
-
267
- it "calculates return value by executing block passed to #and_return" do
268
- @stub.stub(:something).with("a","b","c").and_return { |a,b,c| c+b+a }
269
- expect(@stub.something("a","b","c")).to eq "cba"
270
- verify @stub
271
- end
272
266
  end
273
267
 
274
268
  describe "A method stub with args" do
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ module RSpec::Mocks
4
+ describe Syntax do
5
+ context "when the should syntax is enabled on a non-default syntax host" do
6
+ include_context "with the default mocks syntax"
7
+
8
+ it "continues to warn about the should syntax" do
9
+ my_host = Class.new
10
+ expect(RSpec).to receive(:deprecate)
11
+ Syntax.enable_should(my_host)
12
+
13
+ o = Object.new
14
+ o.should_receive(:bees)
15
+ o.bees
16
+ end
17
+ end
18
+ end
19
+ end
@@ -47,4 +47,3 @@ module RSpec
47
47
  end
48
48
  end
49
49
  end
50
-
@@ -1,28 +1,70 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class LoadedClass
4
+ extend RSpec::Mocks::RubyFeatures
5
+
4
6
  M = :m
5
7
  N = :n
6
8
  INSTANCE = LoadedClass.new
7
9
 
8
- def defined_instance_method; end
9
- def self.defined_class_method; end
10
+ class << self
11
+
12
+ def respond_to?(method_name, include_all = false)
13
+ return true if method_name == :dynamic_class_method
14
+ super
15
+ end
16
+
17
+ def defined_class_method
18
+ end
19
+
20
+ def send
21
+ # fake out!
22
+ end
23
+
24
+ protected
25
+
26
+ def defined_protected_class_method
27
+ end
28
+
29
+ private
30
+
31
+ def defined_private_class_method
32
+ end
33
+
34
+ end
35
+
36
+ def defined_instance_method
37
+ end
10
38
 
11
- def respond_to?(method_name)
39
+ if required_kw_args_supported?
40
+ # Need to eval this since it is invalid syntax on earlier rubies.
41
+ eval <<-RUBY
42
+ def kw_args_method(optional_arg:'hello', required_arg:)
43
+ end
44
+ RUBY
45
+ end
46
+
47
+ def send(*)
48
+ end
49
+
50
+ def respond_to?(method_name, include_all = false)
12
51
  return true if method_name == :dynamic_instance_method
13
52
  super
14
53
  end
15
54
 
16
- def self.respond_to?(method_name)
17
- return true if method_name == :dynamic_class_method
18
- super
55
+ class Nested; end
56
+
57
+ protected
58
+
59
+ def defined_protected_method
19
60
  end
20
61
 
21
- def self.send
22
- # fake out!
62
+ private
63
+
64
+ def defined_private_method
65
+ "wink wink ;)"
23
66
  end
24
67
 
25
- class Nested; end
26
68
  end
27
69
 
28
70
  module RSpec
@@ -51,6 +93,27 @@ module RSpec
51
93
 
52
94
  expect(o.undefined_instance_method(:arg)).to eq(true)
53
95
  end
96
+
97
+ it 'handles classes that are materialized after mocking' do
98
+ stub_const "A::B", Object.new
99
+ o = instance_double "A", :undefined_instance_method => true
100
+
101
+ expect(o.undefined_instance_method).to eq(true)
102
+ end
103
+
104
+ context 'for null objects' do
105
+ let(:o) { instance_double('NonLoadedClass').as_null_object }
106
+
107
+ it 'returns self from any message' do
108
+ expect(o.a.b.c).to be(o)
109
+ end
110
+
111
+ it 'reports it responds to any message' do
112
+ expect(o.respond_to?(:a)).to be true
113
+ expect(o.respond_to?(:a, false)).to be true
114
+ expect(o.respond_to?(:a, true)).to be true
115
+ end
116
+ end
54
117
  end
55
118
 
56
119
  describe 'when doubled class is loaded' do
@@ -79,6 +142,64 @@ module RSpec
79
142
  prevents { o.should_receive(:defined_class_method) }
80
143
  end
81
144
 
145
+ it 'allows `send` to be stubbed if it is defined on the class' do
146
+ o = instance_double('LoadedClass')
147
+ allow(o).to receive(:send).and_return("received")
148
+ expect(o.send(:msg)).to eq("received")
149
+ end
150
+
151
+ it 'gives a descriptive error message for NoMethodError' do
152
+ o = instance_double("LoadedClass")
153
+ expect {
154
+ o.defined_private_method
155
+ }.to raise_error(NoMethodError,
156
+ /Double "LoadedClass \(instance\)"/)
157
+ end
158
+
159
+ describe "method visibility" do
160
+ shared_examples_for "preserves method visibility" do |visibility|
161
+ method_name = :"defined_#{visibility}_method"
162
+
163
+ it "can allow a #{visibility} instance method" do
164
+ o = instance_double('LoadedClass')
165
+ allow(o).to receive(method_name).and_return(3)
166
+ expect(o.send method_name).to eq(3)
167
+ end
168
+
169
+ it "can expect a #{visibility} instance method" do
170
+ o = instance_double('LoadedClass')
171
+ expect(o).to receive(method_name)
172
+ o.send method_name
173
+ end
174
+
175
+ it "preserves #{visibility} visibility when allowing a #{visibility} method" do
176
+ preserves_visibility(method_name, visibility) do
177
+ instance_double('LoadedClass').tap do |o|
178
+ allow(o).to receive(method_name)
179
+ end
180
+ end
181
+ end
182
+
183
+ it "preserves #{visibility} visibility when expecting a #{visibility} method" do
184
+ preserves_visibility(method_name, visibility) do
185
+ instance_double('LoadedClass').tap do |o|
186
+ expect(o).to receive(method_name).at_least(:once)
187
+ o.send(method_name) # to satisfy the expectation
188
+ end
189
+ end
190
+ end
191
+
192
+ it "preserves #{visibility} visibility on a null object" do
193
+ preserves_visibility(method_name, visibility) do
194
+ instance_double('LoadedClass').as_null_object
195
+ end
196
+ end
197
+ end
198
+
199
+ include_examples "preserves method visibility", :private
200
+ include_examples "preserves method visibility", :protected
201
+ end
202
+
82
203
  it 'does not allow dynamic methods to be expected' do
83
204
  # This isn't possible at the moment since an instance of the class
84
205
  # would be required for the verification, and we only have the
@@ -103,7 +224,23 @@ module RSpec
103
224
  o = instance_double('LoadedClass', :defined_instance_method => 25)
104
225
  expect {
105
226
  o.defined_instance_method(:a)
106
- }.to raise_error(ArgumentError)
227
+ }.to raise_error(ArgumentError,
228
+ "Wrong number of arguments. Expected 0, got 1.")
229
+ end
230
+
231
+ if required_kw_args_supported?
232
+ it 'allows keyword arguments' do
233
+ o = instance_double('LoadedClass', :kw_args_method => true)
234
+ expect(o.kw_args_method(:required_arg => 'something')).to eq(true)
235
+ end
236
+
237
+ it 'checks that stubbed methods with required keyword args are ' +
238
+ 'invoked with the required arguments' do
239
+ o = instance_double('LoadedClass', :kw_args_method => true)
240
+ expect {
241
+ o.kw_args_method(:optional_arg => 'something')
242
+ }.to raise_error(ArgumentError)
243
+ end
107
244
  end
108
245
 
109
246
  it 'allows class to be specified by constant' do
@@ -111,11 +248,45 @@ module RSpec
111
248
  expect(o.defined_instance_method).to eq(1)
112
249
  end
113
250
 
114
- it 'only allows defined methods for null objects' do
115
- o = instance_double('LoadedClass').as_null_object
116
-
117
- expect(o.defined_instance_method).to eq(o)
118
- prevents { o.undefined_method }
251
+ context 'for null objects' do
252
+ let(:o) { instance_double('LoadedClass').as_null_object }
253
+
254
+ it 'only allows defined methods' do
255
+ expect(o.defined_instance_method).to eq(o)
256
+ prevents { o.undefined_method }
257
+ prevents { o.send(:undefined_method) }
258
+ prevents { o.__send__(:undefined_method) }
259
+ end
260
+
261
+ it 'reports what public messages it responds to accurately' do
262
+ expect(o.respond_to?(:defined_instance_method)).to be true
263
+ expect(o.respond_to?(:defined_instance_method, true)).to be true
264
+ expect(o.respond_to?(:defined_instance_method, false)).to be true
265
+
266
+ expect(o.respond_to?(:undefined_method)).to be false
267
+ expect(o.respond_to?(:undefined_method, true)).to be false
268
+ expect(o.respond_to?(:undefined_method, false)).to be false
269
+ end
270
+
271
+ it 'reports that it responds to defined private methods when the appropriate arg is passed' do
272
+ expect(o.respond_to?(:defined_private_method)).to be false
273
+ expect(o.respond_to?(:defined_private_method, true)).to be true
274
+ expect(o.respond_to?(:defined_private_method, false)).to be false
275
+ end
276
+
277
+ if RUBY_VERSION.to_f < 2.0 # respond_to?(:protected_method) changed behavior in Ruby 2.0.
278
+ it 'reports that it responds to protected methods' do
279
+ expect(o.respond_to?(:defined_protected_method)).to be true
280
+ expect(o.respond_to?(:defined_protected_method, true)).to be true
281
+ expect(o.respond_to?(:defined_protected_method, false)).to be true
282
+ end
283
+ else
284
+ it 'reports that it responds to protected methods when the appropriate arg is passed' do
285
+ expect(o.respond_to?(:defined_protected_method)).to be false
286
+ expect(o.respond_to?(:defined_protected_method, true)).to be true
287
+ expect(o.respond_to?(:defined_protected_method, false)).to be false
288
+ end
289
+ end
119
290
  end
120
291
  end
121
292
 
@@ -173,6 +344,57 @@ module RSpec
173
344
  prevents { o.should_receive(:defined_instance_method) }
174
345
  end
175
346
 
347
+ it 'gives a descriptive error message for NoMethodError' do
348
+ o = class_double("LoadedClass")
349
+ expect {
350
+ o.defined_private_class_method
351
+ }.to raise_error(NoMethodError, /Double "LoadedClass"/)
352
+ end
353
+
354
+ describe "method visibility" do
355
+ shared_examples_for "preserves method visibility" do |visibility|
356
+ method_name = :"defined_#{visibility}_class_method"
357
+
358
+ it "can allow a #{visibility} instance method" do
359
+ o = class_double('LoadedClass')
360
+ allow(o).to receive(method_name).and_return(3)
361
+ expect(o.send method_name).to eq(3)
362
+ end
363
+
364
+ it "can expect a #{visibility} instance method" do
365
+ o = class_double('LoadedClass')
366
+ expect(o).to receive(method_name)
367
+ o.send method_name
368
+ end
369
+
370
+ it "preserves #{visibility} visibility when allowing a #{visibility} method" do
371
+ preserves_visibility(method_name, visibility) do
372
+ class_double('LoadedClass').tap do |o|
373
+ allow(o).to receive(method_name)
374
+ end
375
+ end
376
+ end
377
+
378
+ it "preserves #{visibility} visibility when expecting a #{visibility} method" do
379
+ preserves_visibility(method_name, visibility) do
380
+ class_double('LoadedClass').tap do |o|
381
+ expect(o).to receive(method_name).at_least(:once)
382
+ o.send(method_name) # to satisfy the expectation
383
+ end
384
+ end
385
+ end
386
+
387
+ it "preserves #{visibility} visibility on a null object" do
388
+ preserves_visibility(method_name, visibility) do
389
+ class_double('LoadedClass').as_null_object
390
+ end
391
+ end
392
+ end
393
+
394
+ include_examples "preserves method visibility", :private
395
+ include_examples "preserves method visibility", :protected
396
+ end
397
+
176
398
  it 'checks that stubbed methods are invoked with the correct arity' do
177
399
  o = class_double('LoadedClass', :defined_class_method => 1)
178
400
  expect {
@@ -210,7 +432,8 @@ module RSpec
210
432
  expect(object).to receive(:defined_class_method)
211
433
 
212
434
  expect(LoadedClass).to eq(object)
213
- ::RSpec::Mocks.space.reset_all
435
+ ::RSpec::Mocks.teardown
436
+ ::RSpec::Mocks.setup
214
437
  expect(LoadedClass).to eq(original)
215
438
  end
216
439
 
@@ -243,6 +466,14 @@ module RSpec
243
466
  class_double(Object.new)
244
467
  }.to raise_error(/Module or String expected/)
245
468
  end
469
+
470
+ it "trying to raise a class_double raises a TypeError", :unless => RUBY_VERSION == '1.9.2' do
471
+ subject = Object.new
472
+ class_double("StubbedError").as_stubbed_const
473
+ allow(subject).to receive(:some_method).and_raise(StubbedError)
474
+ expect { subject.some_method }.to raise_error(TypeError, 'exception class/object expected')
475
+ end
476
+
246
477
  end
247
478
 
248
479
  describe 'object doubles' do
@@ -266,6 +497,8 @@ module RSpec
266
497
 
267
498
  expect(o).to receive(:defined_instance_method)
268
499
  o.defined_instance_method
500
+ expect(o).to receive(:defined_private_method)
501
+ o.send :defined_private_method
269
502
  end
270
503
 
271
504
  it 'can create a double that matches the interface of any arbitrary object' do
@@ -277,6 +510,8 @@ module RSpec
277
510
 
278
511
  expect(o).to receive(:defined_instance_method)
279
512
  o.defined_instance_method
513
+ expect(o).to receive(:defined_private_method)
514
+ o.send :defined_private_method
280
515
  end
281
516
 
282
517
  it 'does not allow transferring constants to an object' do
@@ -308,13 +543,13 @@ module RSpec
308
543
  it 'prevents creation of instance doubles for unloaded constants' do
309
544
  expect {
310
545
  instance_double('LoadedClas')
311
- }.to raise_error(NameError)
546
+ }.to raise_error(VerifyingDoubleNotDefinedError)
312
547
  end
313
548
 
314
549
  it 'prevents creation of class doubles for unloaded constants' do
315
550
  expect {
316
551
  class_double('LoadedClas')
317
- }.to raise_error(NameError)
552
+ }.to raise_error(VerifyingDoubleNotDefinedError)
318
553
  end
319
554
  end
320
555
 
@@ -322,6 +557,34 @@ module RSpec
322
557
  expect { instance_double(1) }.to raise_error(ArgumentError)
323
558
  expect { instance_double(nil) }.to raise_error(ArgumentError)
324
559
  end
560
+
561
+ def preserves_visibility(method_name, visibility)
562
+ double = yield
563
+
564
+ expect {
565
+ # send bypasses visbility, so we use eval instead.
566
+ eval("double.#{method_name}")
567
+ }.to raise_error(NoMethodError, a_message_indicating_visibility_violation(method_name, visibility))
568
+
569
+ expect { double.send(method_name) }.not_to raise_error
570
+ expect { double.__send__(method_name) }.not_to raise_error
571
+
572
+ unless double.null_object?
573
+ # Null object doubles use `method_missing` and so the singleton class
574
+ # doesn't know what methods are defined.
575
+ singleton_class = class << double; self; end
576
+ expect(singleton_class.send("#{visibility}_method_defined?", method_name)).to be true
577
+ end
578
+ end
579
+
580
+ RSpec::Matchers.define :a_message_indicating_visibility_violation do |method_name, visibility|
581
+ match do |msg|
582
+ # This should NOT Be just `msg.match(visibility)` because the method being called
583
+ # has the visibility name in it. We want to ensure it's a message that ruby is
584
+ # stating is of the given visibility.
585
+ msg.match("#{visibility} ") && msg.match(method_name.to_s)
586
+ end
587
+ end
325
588
  end
326
589
  end
327
590
  end