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
@@ -2,32 +2,32 @@ require 'spec_helper'
2
2
 
3
3
  module RSpec
4
4
  module Mocks
5
- describe ArgumentExpectation do
5
+ describe ArgumentListMatcher do
6
6
 
7
7
  it "considers an object that responds to #matches? and #failure_message_for_should to be a matcher" do
8
- argument_expecatation = RSpec::Mocks::ArgumentExpectation.new
8
+ argument_expectation = RSpec::Mocks::ArgumentListMatcher.new
9
9
  obj = double("matcher")
10
10
  obj.stub(:respond_to?).with(:matches?).and_return(true)
11
11
  obj.stub(:respond_to?).with(:failure_message_for_should).and_return(true)
12
- argument_expecatation.is_matcher?(obj).should be_true
12
+ argument_expectation.send(:is_matcher?, obj).should be_true
13
13
  end
14
14
 
15
15
  it "considers an object that responds to #matches? and #failure_message to be a matcher for backward compatibility" do
16
- argument_expecatation = RSpec::Mocks::ArgumentExpectation.new
16
+ argument_expectation = RSpec::Mocks::ArgumentListMatcher.new
17
17
  obj = double("matcher")
18
18
  obj.stub(:respond_to?).with(:matches?).and_return(true)
19
19
  obj.stub(:respond_to?).with(:failure_message_for_should).and_return(false)
20
20
  obj.stub(:respond_to?).with(:failure_message).and_return(true)
21
- argument_expecatation.is_matcher?(obj).should be_true
21
+ argument_expectation.send(:is_matcher?, obj).should be_true
22
22
  end
23
23
 
24
24
  it "does NOT consider an object that only responds to #matches? to be a matcher" do
25
- argument_expecatation = RSpec::Mocks::ArgumentExpectation.new
25
+ argument_expectation = RSpec::Mocks::ArgumentListMatcher.new
26
26
  obj = double("matcher")
27
27
  obj.stub(:respond_to?).with(:matches?).and_return(true)
28
28
  obj.stub(:respond_to?).with(:failure_message_for_should).and_return(false)
29
29
  obj.stub(:respond_to?).with(:failure_message).and_return(false)
30
- argument_expecatation.is_matcher?(obj).should be_false
30
+ argument_expectation.send(:is_matcher?, obj).should be_false
31
31
  end
32
32
  end
33
33
  end
@@ -123,6 +123,20 @@ module RSpec
123
123
  it "passes with at_least(0) with and_return if never called" do
124
124
  @double.should_receive(:do_something).at_least(0).times.and_return true
125
125
  end
126
+
127
+ it "uses a stub value if no value set" do
128
+ @double.stub(:do_something => 'foo')
129
+ @double.should_receive(:do_something).at_least(:once)
130
+ @double.do_something.should eq 'foo'
131
+ @double.do_something.should eq 'foo'
132
+ end
133
+
134
+ it "prefers its own return value over a stub" do
135
+ @double.stub(:do_something => 'foo')
136
+ @double.should_receive(:do_something).at_least(:once).and_return('bar')
137
+ @double.do_something.should eq 'bar'
138
+ @double.do_something.should eq 'bar'
139
+ end
126
140
  end
127
141
  end
128
142
  end
@@ -7,6 +7,14 @@ describe "a double declaration with a block handed to:" do
7
7
  obj.should_receive(:foo) { 'bar' }
8
8
  obj.foo.should eq('bar')
9
9
  end
10
+
11
+ it "works when a multi-return stub has already been set" do
12
+ obj = Object.new
13
+ return_value = Object.new
14
+ obj.stub(:foo).and_return(return_value, nil)
15
+ obj.should_receive(:foo) { return_value }
16
+ obj.foo.should be(return_value)
17
+ end
10
18
  end
11
19
 
12
20
  describe "stub" do
@@ -3,11 +3,17 @@ require 'spec_helper'
3
3
  module RSpec
4
4
  module Mocks
5
5
  describe Mock do
6
- treats_method_missing_as_private :subject => RSpec::Mocks::Mock.new, :noop => false
7
-
8
6
  before(:each) { @double = double("test double") }
9
7
  after(:each) { @double.rspec_reset }
10
8
 
9
+ it "has method_missing as private" do
10
+ RSpec::Mocks::Mock.private_instance_methods.should include_method(:method_missing)
11
+ end
12
+
13
+ it "does not respond_to? method_missing (because it's private)" do
14
+ RSpec::Mocks::Mock.new.should_not respond_to(:method_missing)
15
+ end
16
+
11
17
  it "reports line number of expectation of unreceived message" do
12
18
  expected_error_line = __LINE__; @double.should_receive(:wont_happen).with("x", 3)
13
19
  begin
@@ -176,26 +182,28 @@ module RSpec
176
182
  }.to raise_error(RSpec::Expectations::ExpectationNotMetError)
177
183
  end
178
184
 
179
- it "passes proc to expectation block without an argument", :ruby => '> 1.8.6' do
180
- # We eval this because Ruby 1.8.6's syntax parser barfs on { |&block| ... }
181
- # and prevents the entire spec suite from running.
182
- eval("@double.should_receive(:foo) {|&block| block.call.should eq(:bar)}")
183
- @double.foo { :bar }
184
- end
185
+ context "with Ruby > 1.8.6", :unless => RUBY_VERSION.to_s == '1.8.6' do
186
+ it "passes proc to expectation block without an argument" do
187
+ # We eval this because Ruby 1.8.6's syntax parser barfs on { |&block| ... }
188
+ # and prevents the entire spec suite from running.
189
+ eval("@double.should_receive(:foo) {|&block| block.call.should eq(:bar)}")
190
+ @double.foo { :bar }
191
+ end
185
192
 
186
- it "passes proc to expectation block with an argument", :ruby => '> 1.8.6' do
187
- eval("@double.should_receive(:foo) {|arg, &block| block.call.should eq(:bar)}")
188
- @double.foo(:arg) { :bar }
189
- end
193
+ it "passes proc to expectation block with an argument" do
194
+ eval("@double.should_receive(:foo) {|arg, &block| block.call.should eq(:bar)}")
195
+ @double.foo(:arg) { :bar }
196
+ end
190
197
 
191
- it "passes proc to stub block without an argurment", :ruby => '>1.8.6' do
192
- eval("@double.stub(:foo) {|&block| block.call.should eq(:bar)}")
193
- @double.foo { :bar }
194
- end
198
+ it "passes proc to stub block without an argurment" do
199
+ eval("@double.stub(:foo) {|&block| block.call.should eq(:bar)}")
200
+ @double.foo { :bar }
201
+ end
195
202
 
196
- it "passes proc to stub block with an argument", :ruby => '> 1.8.6' do
197
- eval("@double.stub(:foo) {|arg, &block| block.call.should eq(:bar)}")
198
- @double.foo(:arg) { :bar }
203
+ it "passes proc to stub block with an argument" do
204
+ eval("@double.stub(:foo) {|arg, &block| block.call.should eq(:bar)}")
205
+ @double.foo(:arg) { :bar }
206
+ end
199
207
  end
200
208
 
201
209
  it "fails right away when method defined as never is received" do
@@ -207,7 +215,12 @@ module RSpec
207
215
  end
208
216
 
209
217
  it "raises when told to" do
210
- @double.should_receive(:something).and_raise(RuntimeError)
218
+ @double.should_receive(:something).and_raise(StandardError)
219
+ expect { @double.something }.to raise_error(StandardError)
220
+ end
221
+
222
+ it "raises RuntimeError by default" do
223
+ @double.should_receive(:something).and_raise
211
224
  expect { @double.something }.to raise_error(RuntimeError)
212
225
  end
213
226
 
@@ -75,10 +75,10 @@ module RSpec
75
75
  context "when method is stubbed too" do
76
76
  before { @double.stub(:do_something).and_return :stub_result }
77
77
 
78
- it "uses the stub return value for subsequent calls" do
78
+ it "uses the last value for subsequent calls" do
79
79
  @double.do_something.should equal(11)
80
80
  @double.do_something.should equal(22)
81
- @double.do_something.should equal(:stub_result)
81
+ @double.do_something.should equal(22)
82
82
  @double.rspec_verify
83
83
  end
84
84
 
@@ -15,6 +15,15 @@ module RSpec
15
15
  @double.stub(:foo)
16
16
  @double.should respond_to(:foo)
17
17
  end
18
+
19
+ it "raises an error when interpolated in a string as an integer" do
20
+ # Not sure why, but 1.9.2 raises a different error than 1.8.7 and 1.9.3...
21
+ expected_error = RUBY_VERSION == '1.9.2' ?
22
+ RSpec::Mocks::MockExpectationError :
23
+ TypeError
24
+
25
+ expect { "%i" % @double }.to raise_error(expected_error)
26
+ end
18
27
  end
19
28
 
20
29
  describe "a double acting as a null object" do
@@ -36,6 +45,11 @@ module RSpec
36
45
  @double.something
37
46
  end
38
47
 
48
+ it 'continues to return self from an explicit expectation' do
49
+ @double.should_receive(:bar)
50
+ @double.foo.bar.should be(@double)
51
+ end
52
+
39
53
  it "fails verification when explicit exception not met" do
40
54
  lambda do
41
55
  @double.should_receive(:something)
@@ -59,6 +73,14 @@ module RSpec
59
73
  @double.message(:expected_arg)
60
74
  @double.message(:unexpected_arg)
61
75
  end
76
+
77
+ it "can be interpolated in a string as an integer" do
78
+ # This form of string interpolation calls
79
+ # @double.to_int.to_int.to_int...etc until it gets an integer,
80
+ # and thus gets stuck in an infinite loop unless our double
81
+ # returns an int value from #to_int.
82
+ ("%i" % @double).should eq("0")
83
+ end
62
84
  end
63
85
 
64
86
  describe "#as_null_object" do
@@ -3,27 +3,27 @@ require 'spec_helper'
3
3
  module RSpec
4
4
  module Mocks
5
5
  describe "A chained method stub" do
6
- subject { Object.new }
6
+ let(:object) { Object.new }
7
7
 
8
8
  context "with one method in chain" do
9
9
  context "using and_return" do
10
10
  it "returns expected value from chaining only one method call" do
11
- subject.stub_chain(:msg1).and_return(:return_value)
12
- subject.msg1.should equal(:return_value)
11
+ object.stub_chain(:msg1).and_return(:return_value)
12
+ object.msg1.should equal(:return_value)
13
13
  end
14
14
  end
15
15
 
16
16
  context "using a block" do
17
17
  it "returns the correct value" do
18
- subject.stub_chain(:msg1) { :return_value }
19
- subject.msg1.should equal(:return_value)
18
+ object.stub_chain(:msg1) { :return_value }
19
+ object.msg1.should equal(:return_value)
20
20
  end
21
21
  end
22
22
 
23
23
  context "using a hash" do
24
24
  it "returns the value of the key/value pair" do
25
- subject.stub_chain(:msg1 => :return_value)
26
- subject.msg1.should equal(:return_value)
25
+ object.stub_chain(:msg1 => :return_value)
26
+ object.msg1.should equal(:return_value)
27
27
  end
28
28
  end
29
29
  end
@@ -31,22 +31,22 @@ module RSpec
31
31
  context "with two methods in chain" do
32
32
  context "using and_return" do
33
33
  it "returns expected value from chaining two method calls" do
34
- subject.stub_chain(:msg1, :msg2).and_return(:return_value)
35
- subject.msg1.msg2.should equal(:return_value)
34
+ object.stub_chain(:msg1, :msg2).and_return(:return_value)
35
+ object.msg1.msg2.should equal(:return_value)
36
36
  end
37
37
  end
38
38
 
39
39
  context "using a block" do
40
40
  it "returns the correct value" do
41
- subject.stub_chain(:msg1, :msg2) { :return_value }
42
- subject.msg1.msg2.should equal(:return_value)
41
+ object.stub_chain(:msg1, :msg2) { :return_value }
42
+ object.msg1.msg2.should equal(:return_value)
43
43
  end
44
44
  end
45
45
 
46
46
  context "using a hash" do
47
47
  it "returns the value of the key/value pair" do
48
- subject.stub_chain(:msg1, :msg2 => :return_value)
49
- subject.msg1.msg2.should equal(:return_value)
48
+ object.stub_chain(:msg1, :msg2 => :return_value)
49
+ object.msg1.msg2.should equal(:return_value)
50
50
  end
51
51
  end
52
52
  end
@@ -54,57 +54,57 @@ module RSpec
54
54
  context "with four methods in chain" do
55
55
  context "using and_return" do
56
56
  it "returns expected value from chaining two method calls" do
57
- subject.stub_chain(:msg1, :msg2, :msg3, :msg4).and_return(:return_value)
58
- subject.msg1.msg2.msg3.msg4.should equal(:return_value)
57
+ object.stub_chain(:msg1, :msg2, :msg3, :msg4).and_return(:return_value)
58
+ object.msg1.msg2.msg3.msg4.should equal(:return_value)
59
59
  end
60
60
  end
61
61
 
62
62
  context "using a block" do
63
63
  it "returns the correct value" do
64
- subject.stub_chain(:msg1, :msg2, :msg3, :msg4) { :return_value }
65
- subject.msg1.msg2.msg3.msg4.should equal(:return_value)
64
+ object.stub_chain(:msg1, :msg2, :msg3, :msg4) { :return_value }
65
+ object.msg1.msg2.msg3.msg4.should equal(:return_value)
66
66
  end
67
67
  end
68
68
 
69
69
  context "using a hash" do
70
70
  it "returns the value of the key/value pair" do
71
- subject.stub_chain(:msg1, :msg2, :msg3, :msg4 => :return_value)
72
- subject.msg1.msg2.msg3.msg4.should equal(:return_value)
71
+ object.stub_chain(:msg1, :msg2, :msg3, :msg4 => :return_value)
72
+ object.msg1.msg2.msg3.msg4.should equal(:return_value)
73
73
  end
74
74
  end
75
75
 
76
76
  context "using a hash with a string key" do
77
77
  it "returns the value of the key/value pair" do
78
- subject.stub_chain("msg1.msg2.msg3.msg4" => :return_value)
79
- subject.msg1.msg2.msg3.msg4.should equal(:return_value)
78
+ object.stub_chain("msg1.msg2.msg3.msg4" => :return_value)
79
+ object.msg1.msg2.msg3.msg4.should equal(:return_value)
80
80
  end
81
81
  end
82
82
  end
83
83
 
84
84
  it "returns expected value from chaining four method calls" do
85
- subject.stub_chain(:msg1, :msg2, :msg3, :msg4).and_return(:return_value)
86
- subject.msg1.msg2.msg3.msg4.should equal(:return_value)
85
+ object.stub_chain(:msg1, :msg2, :msg3, :msg4).and_return(:return_value)
86
+ object.msg1.msg2.msg3.msg4.should equal(:return_value)
87
87
  end
88
88
 
89
89
  context "with messages shared across multiple chains" do
90
90
  context "using and_return" do
91
91
  context "starting with the same message" do
92
92
  it "returns expected value" do
93
- subject.stub_chain(:msg1, :msg2, :msg3).and_return(:first)
94
- subject.stub_chain(:msg1, :msg2, :msg4).and_return(:second)
93
+ object.stub_chain(:msg1, :msg2, :msg3).and_return(:first)
94
+ object.stub_chain(:msg1, :msg2, :msg4).and_return(:second)
95
95
 
96
- subject.msg1.msg2.msg3.should equal(:first)
97
- subject.msg1.msg2.msg4.should equal(:second)
96
+ object.msg1.msg2.msg3.should equal(:first)
97
+ object.msg1.msg2.msg4.should equal(:second)
98
98
  end
99
99
  end
100
100
 
101
101
  context "starting with the different messages" do
102
102
  it "returns expected value" do
103
- subject.stub_chain(:msg1, :msg2, :msg3).and_return(:first)
104
- subject.stub_chain(:msg4, :msg2, :msg3).and_return(:second)
103
+ object.stub_chain(:msg1, :msg2, :msg3).and_return(:first)
104
+ object.stub_chain(:msg4, :msg2, :msg3).and_return(:second)
105
105
 
106
- subject.msg1.msg2.msg3.should equal(:first)
107
- subject.msg4.msg2.msg3.should equal(:second)
106
+ object.msg1.msg2.msg3.should equal(:first)
107
+ object.msg4.msg2.msg3.should equal(:second)
108
108
  end
109
109
  end
110
110
  end
@@ -112,37 +112,37 @@ module RSpec
112
112
  context "using => value" do
113
113
  context "starting with the same message" do
114
114
  it "returns expected value" do
115
- subject.stub_chain(:msg1, :msg2, :msg3 => :first)
116
- subject.stub_chain(:msg1, :msg2, :msg4 => :second)
115
+ object.stub_chain(:msg1, :msg2, :msg3 => :first)
116
+ object.stub_chain(:msg1, :msg2, :msg4 => :second)
117
117
 
118
- subject.msg1.msg2.msg3.should equal(:first)
119
- subject.msg1.msg2.msg4.should equal(:second)
118
+ object.msg1.msg2.msg3.should equal(:first)
119
+ object.msg1.msg2.msg4.should equal(:second)
120
120
  end
121
121
  end
122
122
 
123
123
  context "starting with different messages" do
124
124
  it "returns expected value" do
125
- subject.stub_chain(:msg1, :msg2, :msg3 => :first)
126
- subject.stub_chain(:msg4, :msg2, :msg3 => :second)
125
+ object.stub_chain(:msg1, :msg2, :msg3 => :first)
126
+ object.stub_chain(:msg4, :msg2, :msg3 => :second)
127
127
 
128
- subject.msg1.msg2.msg3.should equal(:first)
129
- subject.msg4.msg2.msg3.should equal(:second)
128
+ object.msg1.msg2.msg3.should equal(:first)
129
+ object.msg4.msg2.msg3.should equal(:second)
130
130
  end
131
131
  end
132
132
  end
133
133
  end
134
134
 
135
135
  it "returns expected value when chain is a dot separated string, like stub_chain('msg1.msg2.msg3')" do
136
- subject.stub_chain("msg1.msg2.msg3").and_return(:return_value)
137
- subject.msg1.msg2.msg3.should equal(:return_value)
136
+ object.stub_chain("msg1.msg2.msg3").and_return(:return_value)
137
+ object.msg1.msg2.msg3.should equal(:return_value)
138
138
  end
139
139
 
140
140
  it "returns expected value from two chains with shared messages at the beginning" do
141
- subject.stub_chain(:msg1, :msg2, :msg3, :msg4).and_return(:first)
142
- subject.stub_chain(:msg1, :msg2, :msg3, :msg5).and_return(:second)
141
+ object.stub_chain(:msg1, :msg2, :msg3, :msg4).and_return(:first)
142
+ object.stub_chain(:msg1, :msg2, :msg3, :msg5).and_return(:second)
143
143
 
144
- subject.msg1.msg2.msg3.msg4.should equal(:first)
145
- subject.msg1.msg2.msg3.msg5.should equal(:second)
144
+ object.msg1.msg2.msg3.msg4.should equal(:first)
145
+ object.msg1.msg2.msg3.msg5.should equal(:second)
146
146
  end
147
147
  end
148
148
  end
@@ -0,0 +1,309 @@
1
+ require 'spec_helper'
2
+
3
+ TOP_LEVEL_VALUE_CONST = 7
4
+
5
+ class TestClass
6
+ M = :m
7
+ N = :n
8
+
9
+ class Nested
10
+ class NestedEvenMore
11
+ end
12
+ end
13
+ end
14
+
15
+ module RSpec
16
+ module Mocks
17
+ describe "Constant Stubbing" do
18
+ include RSpec::Mocks::RecursiveConstMethods
19
+
20
+ def reset_rspec_mocks
21
+ ::RSpec::Mocks.space.reset_all
22
+ end
23
+
24
+ shared_examples_for "loaded constant stubbing" do |const_name|
25
+ let!(:original_const_value) { const }
26
+ after { change_const_value_to(original_const_value) }
27
+
28
+ define_method :const do
29
+ recursive_const_get(const_name)
30
+ end
31
+
32
+ define_method :parent_const do
33
+ recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
34
+ end
35
+
36
+ define_method :last_const_part do
37
+ const_name.split('::').last
38
+ end
39
+
40
+ def change_const_value_to(value)
41
+ parent_const.send(:remove_const, last_const_part)
42
+ parent_const.const_set(last_const_part, value)
43
+ end
44
+
45
+ it 'allows it to be stubbed' do
46
+ const.should_not eq(7)
47
+ stub_const(const_name, 7)
48
+ const.should eq(7)
49
+ end
50
+
51
+ it 'resets it to its original value when rspec clears its mocks' do
52
+ original_value = const
53
+ original_value.should_not eq(:a)
54
+ stub_const(const_name, :a)
55
+ reset_rspec_mocks
56
+ const.should be(original_value)
57
+ end
58
+
59
+ it 'returns the stubbed value' do
60
+ orig_value = const
61
+ stub_const(const_name, 7).should eq(7)
62
+ end
63
+ end
64
+
65
+ shared_examples_for "unloaded constant stubbing" do |const_name|
66
+ before { recursive_const_defined?(const_name).should be_false }
67
+
68
+ define_method :const do
69
+ recursive_const_get(const_name)
70
+ end
71
+
72
+ define_method :parent_const do
73
+ recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
74
+ end
75
+
76
+ define_method :last_const_part do
77
+ const_name.split('::').last
78
+ end
79
+
80
+ it 'allows it to be stubbed' do
81
+ stub_const(const_name, 7)
82
+ const.should eq(7)
83
+ end
84
+
85
+ it 'removes the constant when rspec clears its mocks' do
86
+ stub_const(const_name, 7)
87
+ reset_rspec_mocks
88
+ recursive_const_defined?(const_name).should be_false
89
+ end
90
+
91
+ it 'returns the stubbed value' do
92
+ stub_const(const_name, 7).should eq(7)
93
+ end
94
+
95
+ it 'ignores the :transfer_nested_constants option if passed' do
96
+ stub = Module.new
97
+ stub_const(const_name, stub, :transfer_nested_constants => true)
98
+ stub.constants.should eq([])
99
+ end
100
+ end
101
+
102
+ describe "#stub_const" do
103
+ context 'for a loaded unnested constant' do
104
+ it_behaves_like "loaded constant stubbing", "TestClass"
105
+
106
+ it 'can be stubbed multiple times but still restores the original value properly' do
107
+ orig_value = TestClass
108
+ stub1, stub2 = Module.new, Module.new
109
+ stub_const("TestClass", stub1)
110
+ stub_const("TestClass", stub2)
111
+
112
+ reset_rspec_mocks
113
+ TestClass.should be(orig_value)
114
+ end
115
+
116
+ it 'allows nested constants to be transferred to a stub module' do
117
+ tc_nested = TestClass::Nested
118
+ stub = Module.new
119
+ stub_const("TestClass", stub, :transfer_nested_constants => true)
120
+ stub::M.should eq(:m)
121
+ stub::N.should eq(:n)
122
+ stub::Nested.should be(tc_nested)
123
+ end
124
+
125
+ it 'allows nested constants to be selectively transferred to a stub module' do
126
+ stub = Module.new
127
+ stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
128
+ stub::M.should eq(:m)
129
+ stub::N.should eq(:n)
130
+ defined?(stub::Nested).should be_false
131
+ end
132
+
133
+ it 'raises an error if asked to transfer nested constants but given an object that does not support them' do
134
+ original_tc = TestClass
135
+ stub = Object.new
136
+ expect {
137
+ stub_const("TestClass", stub, :transfer_nested_constants => true)
138
+ }.to raise_error(ArgumentError)
139
+
140
+ TestClass.should be(original_tc)
141
+
142
+ expect {
143
+ stub_const("TestClass", stub, :transfer_nested_constants => [:M])
144
+ }.to raise_error(ArgumentError)
145
+
146
+ TestClass.should be(original_tc)
147
+ end
148
+
149
+ it 'raises an error if asked to transfer nested constants on a constant that does not support nested constants' do
150
+ stub = Module.new
151
+ expect {
152
+ stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => true)
153
+ }.to raise_error(ArgumentError)
154
+
155
+ TOP_LEVEL_VALUE_CONST.should eq(7)
156
+
157
+ expect {
158
+ stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => [:M])
159
+ }.to raise_error(ArgumentError)
160
+
161
+ TOP_LEVEL_VALUE_CONST.should eq(7)
162
+ end
163
+
164
+ it 'raises an error if asked to transfer a nested constant that is not defined' do
165
+ original_tc = TestClass
166
+ defined?(TestClass::V).should be_false
167
+ stub = Module.new
168
+
169
+ expect {
170
+ stub_const("TestClass", stub, :transfer_nested_constants => [:V])
171
+ }.to raise_error(/cannot transfer nested constant.*V/i)
172
+
173
+ TestClass.should be(original_tc)
174
+ end
175
+ end
176
+
177
+ context 'for a loaded nested constant' do
178
+ it_behaves_like "loaded constant stubbing", "TestClass::Nested"
179
+ end
180
+
181
+ context 'for a loaded deeply nested constant' do
182
+ it_behaves_like "loaded constant stubbing", "TestClass::Nested::NestedEvenMore"
183
+ end
184
+
185
+ context 'for an unloaded unnested constant' do
186
+ it_behaves_like "unloaded constant stubbing", "X"
187
+ end
188
+
189
+ context 'for an unloaded nested constant' do
190
+ it_behaves_like "unloaded constant stubbing", "X::Y"
191
+
192
+ it 'removes the root constant when rspec clears its mocks' do
193
+ defined?(X).should be_false
194
+ stub_const("X::Y", 7)
195
+ reset_rspec_mocks
196
+ defined?(X).should be_false
197
+ end
198
+ end
199
+
200
+ context 'for an unloaded deeply nested constant' do
201
+ it_behaves_like "unloaded constant stubbing", "X::Y::Z"
202
+
203
+ it 'removes the root constant when rspec clears its mocks' do
204
+ defined?(X).should be_false
205
+ stub_const("X::Y::Z", 7)
206
+ reset_rspec_mocks
207
+ defined?(X).should be_false
208
+ end
209
+ end
210
+
211
+ context 'for an unloaded constant nested within a loaded constant' do
212
+ it_behaves_like "unloaded constant stubbing", "TestClass::X"
213
+
214
+ it 'removes the unloaded constant but leaves the loaded constant when rspec resets its mocks' do
215
+ defined?(TestClass).should be_true
216
+ defined?(TestClass::X).should be_false
217
+ stub_const("TestClass::X", 7)
218
+ reset_rspec_mocks
219
+ defined?(TestClass).should be_true
220
+ defined?(TestClass::X).should be_false
221
+ end
222
+
223
+ it 'raises a helpful error if it cannot be stubbed due to an intermediary constant that is not a module' do
224
+ TestClass::M.should be_a(Symbol)
225
+ expect { stub_const("TestClass::M::X", 5) }.to raise_error(/cannot stub/i)
226
+ end
227
+ end
228
+
229
+ context 'for an unloaded constant nested deeply within a deeply nested loaded constant' do
230
+ it_behaves_like "unloaded constant stubbing", "TestClass::Nested::NestedEvenMore::X::Y::Z"
231
+
232
+ it 'removes the first unloaded constant but leaves the loaded nested constant when rspec resets its mocks' do
233
+ defined?(TestClass::Nested::NestedEvenMore).should be_true
234
+ defined?(TestClass::Nested::NestedEvenMore::X).should be_false
235
+ stub_const("TestClass::Nested::NestedEvenMore::X::Y::Z", 7)
236
+ reset_rspec_mocks
237
+ defined?(TestClass::Nested::NestedEvenMore).should be_true
238
+ defined?(TestClass::Nested::NestedEvenMore::X).should be_false
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ describe Constant do
245
+ describe ".original" do
246
+ context 'for a previously defined unstubbed constant' do
247
+ let(:const) { Constant.original("TestClass::M") }
248
+
249
+ it("exposes its name") { const.name.should eq("TestClass::M") }
250
+ it("indicates it was previously defined") { const.should be_previously_defined }
251
+ it("indicates it has not been stubbed") { const.should_not be_stubbed }
252
+ it("exposes its original value") { const.original_value.should eq(:m) }
253
+ end
254
+
255
+ context 'for a previously defined stubbed constant' do
256
+ before { stub_const("TestClass::M", :other) }
257
+ let(:const) { Constant.original("TestClass::M") }
258
+
259
+ it("exposes its name") { const.name.should eq("TestClass::M") }
260
+ it("indicates it was previously defined") { const.should be_previously_defined }
261
+ it("indicates it has been stubbed") { const.should be_stubbed }
262
+ it("exposes its original value") { const.original_value.should eq(:m) }
263
+ end
264
+
265
+ context 'for a previously undefined stubbed constant' do
266
+ before { stub_const("TestClass::Undefined", :other) }
267
+ let(:const) { Constant.original("TestClass::Undefined") }
268
+
269
+ it("exposes its name") { const.name.should eq("TestClass::Undefined") }
270
+ it("indicates it was not previously defined") { const.should_not be_previously_defined }
271
+ it("indicates it has been stubbed") { const.should be_stubbed }
272
+ it("returns nil for the original value") { const.original_value.should be_nil }
273
+ end
274
+
275
+ context 'for a previously undefined unstubbed constant' do
276
+ let(:const) { Constant.original("TestClass::Undefined") }
277
+
278
+ it("exposes its name") { const.name.should eq("TestClass::Undefined") }
279
+ it("indicates it was not previously defined") { const.should_not be_previously_defined }
280
+ it("indicates it has not been stubbed") { const.should_not be_stubbed }
281
+ it("returns nil for the original value") { const.original_value.should be_nil }
282
+ end
283
+
284
+ context 'for a previously defined constant that has been stubbed twice' do
285
+ before { stub_const("TestClass::M", 1) }
286
+ before { stub_const("TestClass::M", 2) }
287
+ let(:const) { Constant.original("TestClass::M") }
288
+
289
+ it("exposes its name") { const.name.should eq("TestClass::M") }
290
+ it("indicates it was previously defined") { const.should be_previously_defined }
291
+ it("indicates it has been stubbed") { const.should be_stubbed }
292
+ it("exposes its original value") { const.original_value.should eq(:m) }
293
+ end
294
+
295
+ context 'for a previously undefined constant that has been stubbed twice' do
296
+ before { stub_const("TestClass::Undefined", 1) }
297
+ before { stub_const("TestClass::Undefined", 2) }
298
+ let(:const) { Constant.original("TestClass::Undefined") }
299
+
300
+ it("exposes its name") { const.name.should eq("TestClass::Undefined") }
301
+ it("indicates it was not previously defined") { const.should_not be_previously_defined }
302
+ it("indicates it has been stubbed") { const.should be_stubbed }
303
+ it("returns nil for the original value") { const.original_value.should be_nil }
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
309
+