spy 0.0.1 → 0.1.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.
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/README.md +90 -44
- data/lib/spy.rb +85 -207
- data/lib/spy/agency.rb +65 -0
- data/lib/spy/constant.rb +67 -0
- data/lib/spy/core_ext/marshal.rb +28 -0
- data/lib/spy/double.rb +1 -1
- data/lib/spy/nest.rb +45 -0
- data/lib/spy/subroutine.rb +239 -0
- data/lib/spy/version.rb +2 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/spy/and_call_original_spec.rb +1 -1
- data/spec/spy/and_yield_spec.rb +23 -14
- data/spec/spy/hash_excluding_matcher_spec.rb +55 -59
- data/spec/spy/hash_including_matcher_spec.rb +69 -73
- data/spec/spy/mock_spec.rb +585 -589
- data/spec/spy/mutate_const_spec.rb +407 -367
- data/spec/spy/partial_mock_spec.rb +106 -162
- data/spec/spy/passing_argument_matchers_spec.rb +134 -136
- data/spec/spy/serialization_spec.rb +80 -74
- data/spec/spy/stub_implementation_spec.rb +14 -12
- data/spec/spy/stub_spec.rb +12 -12
- data/spec/spy/test_double_spec.rb +38 -41
- data/spy.gemspec +2 -1
- data/test/integration/test_constant_spying.rb +58 -0
- data/test/integration/test_subroutine_spying.rb +40 -0
- data/test/spy/test_double.rb +1 -1
- data/test/spy/test_subroutine.rb +191 -0
- data/test/support/pen.rb +50 -0
- data/test/test_helper.rb +4 -0
- metadata +33 -9
- data/TODO.md +0 -8
- data/lib/spy/dsl.rb +0 -7
- data/spec/spy/multiple_return_value_spec.rb +0 -119
- data/test/test_spy.rb +0 -258
@@ -16,466 +16,506 @@ class TestSubClass < TestClass
|
|
16
16
|
P = :p
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
module Mocks
|
21
|
-
describe "Constant Mutating", :broken do
|
22
|
-
include RSpec::Mocks::RecursiveConstMethods
|
19
|
+
require "rspec/mocks/mutate_const"
|
23
20
|
|
24
|
-
|
25
|
-
|
21
|
+
module Spy
|
22
|
+
describe "Constant Mutating" do
|
23
|
+
|
24
|
+
require "rspec/mocks/mutate_const"
|
25
|
+
include RSpec::Mocks::RecursiveConstMethods
|
26
|
+
|
27
|
+
def reset_rspec_mocks
|
28
|
+
Spy.teardown
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_const(const_name)
|
32
|
+
parent_const = recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
|
33
|
+
Spy.on_const(parent_const, const_name.split('::').last.to_sym)
|
34
|
+
end
|
35
|
+
|
36
|
+
def stub_const(const_name, value)
|
37
|
+
on_const(const_name).and_return(value)
|
38
|
+
value
|
39
|
+
end
|
40
|
+
|
41
|
+
def hide_const(const_name)
|
42
|
+
on_const(const_name).and_hide
|
43
|
+
end
|
44
|
+
|
45
|
+
shared_context "constant example methods" do |const_name|
|
46
|
+
define_method :const do
|
47
|
+
recursive_const_get(const_name)
|
26
48
|
end
|
27
49
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
50
|
+
define_method :parent_const do
|
51
|
+
recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
|
52
|
+
end
|
32
53
|
|
33
|
-
|
34
|
-
|
35
|
-
|
54
|
+
define_method :last_const_part do
|
55
|
+
const_name.split('::').last
|
56
|
+
end
|
57
|
+
end
|
36
58
|
|
37
|
-
|
38
|
-
|
39
|
-
|
59
|
+
shared_examples_for "loaded constant stubbing" do |const_name|
|
60
|
+
include_context "constant example methods", const_name
|
61
|
+
|
62
|
+
let!(:original_const_value) { const }
|
63
|
+
after { change_const_value_to(original_const_value) }
|
64
|
+
|
65
|
+
def change_const_value_to(value)
|
66
|
+
parent_const.send(:remove_const, last_const_part)
|
67
|
+
parent_const.const_set(last_const_part, value)
|
40
68
|
end
|
41
69
|
|
42
|
-
|
43
|
-
|
70
|
+
it 'allows it to be stubbed' do
|
71
|
+
expect(const).not_to eq(7)
|
72
|
+
stub_const(const_name, 7)
|
73
|
+
expect(const).to eq(7)
|
74
|
+
end
|
44
75
|
|
45
|
-
|
46
|
-
|
76
|
+
it 'resets it to its original value when rspec clears its mocks' do
|
77
|
+
original_value = const
|
78
|
+
expect(original_value).not_to eq(:a)
|
79
|
+
stub_const(const_name, :a)
|
80
|
+
reset_rspec_mocks
|
81
|
+
expect(const).to be(original_value)
|
82
|
+
end
|
47
83
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
84
|
+
it 'returns the stubbed value' do
|
85
|
+
expect(stub_const(const_name, 7)).to eq(7)
|
86
|
+
end
|
87
|
+
end
|
52
88
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
89
|
+
shared_examples_for "loaded constant hiding" do |const_name|
|
90
|
+
before do
|
91
|
+
expect(recursive_const_defined?(const_name)).to be_true
|
92
|
+
end
|
58
93
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
reset_rspec_mocks
|
64
|
-
expect(const).to be(original_value)
|
65
|
-
end
|
94
|
+
it 'allows it to be hidden' do
|
95
|
+
hide_const(const_name)
|
96
|
+
expect(recursive_const_defined?(const_name)).to be_false
|
97
|
+
end
|
66
98
|
|
67
|
-
|
68
|
-
|
69
|
-
|
99
|
+
it 'resets the constant when rspec clear its mocks' do
|
100
|
+
hide_const(const_name)
|
101
|
+
reset_rspec_mocks
|
102
|
+
expect(recursive_const_defined?(const_name)).to be_true
|
70
103
|
end
|
71
104
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
105
|
+
it 'returns nil' do
|
106
|
+
expect(hide_const(const_name)).to be_nil
|
107
|
+
end
|
108
|
+
end
|
76
109
|
|
77
|
-
|
78
|
-
|
79
|
-
expect(recursive_const_defined?(const_name)).to be_false
|
80
|
-
end
|
110
|
+
shared_examples_for "unloaded constant stubbing" do |const_name|
|
111
|
+
include_context "constant example methods", const_name
|
81
112
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
expect(recursive_const_defined?(const_name)).to be_true
|
86
|
-
end
|
113
|
+
before do
|
114
|
+
expect(recursive_const_defined?(const_name)).to be_false
|
115
|
+
end
|
87
116
|
|
88
|
-
|
89
|
-
|
90
|
-
|
117
|
+
it 'allows it to be stubbed' do
|
118
|
+
stub_const(const_name, 7)
|
119
|
+
expect(const).to eq(7)
|
91
120
|
end
|
92
121
|
|
93
|
-
|
94
|
-
|
122
|
+
it 'removes the constant when rspec clears its mocks' do
|
123
|
+
stub_const(const_name, 7)
|
124
|
+
reset_rspec_mocks
|
125
|
+
expect(recursive_const_defined?(const_name)).to be_false
|
126
|
+
end
|
95
127
|
|
96
|
-
|
97
|
-
|
98
|
-
|
128
|
+
it 'returns the stubbed value' do
|
129
|
+
expect(stub_const(const_name, 7)).to eq(7)
|
130
|
+
end
|
99
131
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
132
|
+
it 'ignores the :transfer_nested_constants option if passed' do
|
133
|
+
stub = Module.new
|
134
|
+
stub_const(const_name, stub, :transfer_nested_constants => true)
|
135
|
+
expect(stub.constants).to eq([])
|
136
|
+
end
|
137
|
+
end
|
104
138
|
|
105
|
-
|
106
|
-
|
107
|
-
reset_rspec_mocks
|
108
|
-
expect(recursive_const_defined?(const_name)).to be_false
|
109
|
-
end
|
139
|
+
shared_examples_for "unloaded constant hiding" do |const_name|
|
140
|
+
include_context "constant example methods", const_name
|
110
141
|
|
111
|
-
|
112
|
-
|
113
|
-
|
142
|
+
before do
|
143
|
+
expect(recursive_const_defined?(const_name)).to be_false
|
144
|
+
end
|
114
145
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
expect(stub.constants).to eq([])
|
119
|
-
end
|
146
|
+
it 'allows it to be hidden, though the operation has no effect' do
|
147
|
+
hide_const(const_name)
|
148
|
+
expect(recursive_const_defined?(const_name)).to be_false
|
120
149
|
end
|
121
150
|
|
122
|
-
|
123
|
-
|
151
|
+
it 'remains undefined after rspec clears its mocks' do
|
152
|
+
hide_const(const_name)
|
153
|
+
reset_rspec_mocks
|
154
|
+
expect(recursive_const_defined?(const_name)).to be_false
|
155
|
+
end
|
124
156
|
|
125
|
-
|
126
|
-
|
127
|
-
|
157
|
+
it 'returns nil' do
|
158
|
+
expect(hide_const(const_name)).to be_nil
|
159
|
+
end
|
160
|
+
end
|
128
161
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
162
|
+
describe "#hide_const" do
|
163
|
+
context 'for a loaded nested constant' do
|
164
|
+
it_behaves_like "loaded constant hiding", "TestClass::Nested"
|
165
|
+
end
|
133
166
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
167
|
+
context 'for a loaded constant prefixed with ::' do
|
168
|
+
it_behaves_like 'loaded constant hiding', "::TestClass"
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'for an unloaded constant with nested name that matches a top-level constant' do
|
172
|
+
it_behaves_like "unloaded constant hiding", "TestClass::Hash"
|
173
|
+
|
174
|
+
it 'does not hide the top-level constant' do
|
175
|
+
top_level_hash = ::Hash
|
176
|
+
|
177
|
+
hide_const("TestClass::Hash")
|
178
|
+
expect(::Hash).to equal(top_level_hash)
|
138
179
|
end
|
139
180
|
|
140
|
-
it '
|
141
|
-
|
181
|
+
it 'does not affect the ability to access the top-level constant from nested contexts', :silence_warnings do
|
182
|
+
top_level_hash = ::Hash
|
183
|
+
|
184
|
+
hide_const("TestClass::Hash")
|
185
|
+
expect(TestClass::Hash).to equal(top_level_hash)
|
142
186
|
end
|
143
187
|
end
|
144
188
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
189
|
+
context 'for a loaded deeply nested constant' do
|
190
|
+
it_behaves_like "loaded constant hiding", "TestClass::Nested::NestedEvenMore"
|
191
|
+
end
|
149
192
|
|
150
|
-
|
151
|
-
|
152
|
-
|
193
|
+
context 'for an unloaded unnested constant' do
|
194
|
+
it_behaves_like "unloaded constant hiding", "X"
|
195
|
+
end
|
153
196
|
|
154
|
-
|
155
|
-
|
197
|
+
context 'for an unloaded nested constant' do
|
198
|
+
it_behaves_like "unloaded constant hiding", "X::Y"
|
199
|
+
end
|
156
200
|
|
157
|
-
|
158
|
-
|
201
|
+
it 'can be hidden multiple times but still restores the original value properly' do
|
202
|
+
orig_value = TestClass
|
203
|
+
hide_const("TestClass")
|
204
|
+
hide_const("TestClass")
|
159
205
|
|
160
|
-
|
161
|
-
|
162
|
-
|
206
|
+
reset_rspec_mocks
|
207
|
+
expect(TestClass).to be(orig_value)
|
208
|
+
end
|
163
209
|
|
164
|
-
|
165
|
-
|
210
|
+
it 'allows a constant to be hidden, then stubbed, restoring it to its original value properly' do
|
211
|
+
orig_value = TOP_LEVEL_VALUE_CONST
|
166
212
|
|
167
|
-
|
168
|
-
|
169
|
-
end
|
170
|
-
end
|
213
|
+
hide_const("TOP_LEVEL_VALUE_CONST")
|
214
|
+
expect(recursive_const_defined?("TOP_LEVEL_VALUE_CONST")).to be_false
|
171
215
|
|
172
|
-
|
173
|
-
|
174
|
-
end
|
216
|
+
stub_const("TOP_LEVEL_VALUE_CONST", 12345)
|
217
|
+
expect(TOP_LEVEL_VALUE_CONST).to eq 12345
|
175
218
|
|
176
|
-
|
177
|
-
|
178
|
-
|
219
|
+
reset_rspec_mocks
|
220
|
+
expect(TOP_LEVEL_VALUE_CONST).to eq orig_value
|
221
|
+
end
|
222
|
+
end
|
179
223
|
|
180
|
-
|
181
|
-
|
182
|
-
|
224
|
+
describe "#stub_const" do
|
225
|
+
context 'for a loaded unnested constant' do
|
226
|
+
it_behaves_like "loaded constant stubbing", "TestClass"
|
183
227
|
|
184
|
-
it 'can be
|
228
|
+
it 'can be stubbed multiple times but still restores the original value properly' do
|
185
229
|
orig_value = TestClass
|
186
|
-
|
187
|
-
|
230
|
+
stub1, stub2 = Module.new, Module.new
|
231
|
+
stub_const("TestClass", stub1)
|
232
|
+
stub_const("TestClass", stub2)
|
188
233
|
|
189
234
|
reset_rspec_mocks
|
190
235
|
expect(TestClass).to be(orig_value)
|
191
236
|
end
|
192
237
|
|
193
|
-
it 'allows
|
194
|
-
|
238
|
+
it 'allows nested constants to be transferred to a stub module' do
|
239
|
+
tc_nested = TestClass::Nested
|
240
|
+
stub = Module.new
|
241
|
+
stub_const("TestClass", stub, :transfer_nested_constants => true)
|
242
|
+
expect(stub::M).to eq(:m)
|
243
|
+
expect(stub::N).to eq(:n)
|
244
|
+
expect(stub::Nested).to be(tc_nested)
|
245
|
+
end
|
195
246
|
|
196
|
-
|
197
|
-
|
247
|
+
it 'does not transfer nested constants that are inherited from a superclass' do
|
248
|
+
stub = Module.new
|
249
|
+
stub_const("TestSubClass", stub, :transfer_nested_constants => true)
|
250
|
+
expect(stub::P).to eq(:p)
|
251
|
+
expect(defined?(stub::M)).to be_false
|
252
|
+
expect(defined?(stub::N)).to be_false
|
253
|
+
end
|
198
254
|
|
199
|
-
|
200
|
-
|
255
|
+
it 'raises an error when asked to transfer a nested inherited constant' do
|
256
|
+
original_tsc = TestSubClass
|
201
257
|
|
202
|
-
|
203
|
-
|
258
|
+
expect {
|
259
|
+
stub_const("TestSubClass", Module.new, :transfer_nested_constants => [:M])
|
260
|
+
}.to raise_error(ArgumentError)
|
261
|
+
|
262
|
+
expect(TestSubClass).to be(original_tsc)
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'allows nested constants to be selectively transferred to a stub module' do
|
266
|
+
stub = Module.new
|
267
|
+
stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
|
268
|
+
expect(stub::M).to eq(:m)
|
269
|
+
expect(stub::N).to eq(:n)
|
270
|
+
expect(defined?(stub::Nested)).to be_false
|
204
271
|
end
|
205
|
-
end
|
206
272
|
|
207
|
-
|
208
|
-
|
209
|
-
|
273
|
+
it 'raises an error if asked to transfer nested constants but given an object that does not support them' do
|
274
|
+
original_tc = TestClass
|
275
|
+
stub = Object.new
|
276
|
+
expect {
|
277
|
+
stub_const("TestClass", stub, :transfer_nested_constants => true)
|
278
|
+
}.to raise_error(ArgumentError)
|
210
279
|
|
211
|
-
|
212
|
-
orig_value = TestClass
|
213
|
-
stub1, stub2 = Module.new, Module.new
|
214
|
-
stub_const("TestClass", stub1)
|
215
|
-
stub_const("TestClass", stub2)
|
280
|
+
expect(TestClass).to be(original_tc)
|
216
281
|
|
217
|
-
|
218
|
-
|
219
|
-
|
282
|
+
expect {
|
283
|
+
stub_const("TestClass", stub, :transfer_nested_constants => [:M])
|
284
|
+
}.to raise_error(ArgumentError)
|
220
285
|
|
221
|
-
|
222
|
-
tc_nested = TestClass::Nested
|
223
|
-
stub = Module.new
|
224
|
-
stub_const("TestClass", stub, :transfer_nested_constants => true)
|
225
|
-
expect(stub::M).to eq(:m)
|
226
|
-
expect(stub::N).to eq(:n)
|
227
|
-
expect(stub::Nested).to be(tc_nested)
|
228
|
-
end
|
229
|
-
|
230
|
-
it 'does not transfer nested constants that are inherited from a superclass' do
|
231
|
-
stub = Module.new
|
232
|
-
stub_const("TestSubClass", stub, :transfer_nested_constants => true)
|
233
|
-
expect(stub::P).to eq(:p)
|
234
|
-
expect(defined?(stub::M)).to be_false
|
235
|
-
expect(defined?(stub::N)).to be_false
|
236
|
-
end
|
237
|
-
|
238
|
-
it 'raises an error when asked to transfer a nested inherited constant' do
|
239
|
-
original_tsc = TestSubClass
|
240
|
-
|
241
|
-
expect {
|
242
|
-
stub_const("TestSubClass", Module.new, :transfer_nested_constants => [:M])
|
243
|
-
}.to raise_error(ArgumentError)
|
244
|
-
|
245
|
-
expect(TestSubClass).to be(original_tsc)
|
246
|
-
end
|
247
|
-
|
248
|
-
it 'allows nested constants to be selectively transferred to a stub module' do
|
249
|
-
stub = Module.new
|
250
|
-
stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
|
251
|
-
expect(stub::M).to eq(:m)
|
252
|
-
expect(stub::N).to eq(:n)
|
253
|
-
expect(defined?(stub::Nested)).to be_false
|
254
|
-
end
|
255
|
-
|
256
|
-
it 'raises an error if asked to transfer nested constants but given an object that does not support them' do
|
257
|
-
original_tc = TestClass
|
258
|
-
stub = Object.new
|
259
|
-
expect {
|
260
|
-
stub_const("TestClass", stub, :transfer_nested_constants => true)
|
261
|
-
}.to raise_error(ArgumentError)
|
262
|
-
|
263
|
-
expect(TestClass).to be(original_tc)
|
264
|
-
|
265
|
-
expect {
|
266
|
-
stub_const("TestClass", stub, :transfer_nested_constants => [:M])
|
267
|
-
}.to raise_error(ArgumentError)
|
268
|
-
|
269
|
-
expect(TestClass).to be(original_tc)
|
270
|
-
end
|
271
|
-
|
272
|
-
it 'raises an error if asked to transfer nested constants on a constant that does not support nested constants' do
|
273
|
-
stub = Module.new
|
274
|
-
expect {
|
275
|
-
stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => true)
|
276
|
-
}.to raise_error(ArgumentError)
|
277
|
-
|
278
|
-
expect(TOP_LEVEL_VALUE_CONST).to eq(7)
|
279
|
-
|
280
|
-
expect {
|
281
|
-
stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => [:M])
|
282
|
-
}.to raise_error(ArgumentError)
|
283
|
-
|
284
|
-
expect(TOP_LEVEL_VALUE_CONST).to eq(7)
|
285
|
-
end
|
286
|
-
|
287
|
-
it 'raises an error if asked to transfer a nested constant that is not defined' do
|
288
|
-
original_tc = TestClass
|
289
|
-
expect(defined?(TestClass::V)).to be_false
|
290
|
-
stub = Module.new
|
291
|
-
|
292
|
-
expect {
|
293
|
-
stub_const("TestClass", stub, :transfer_nested_constants => [:V])
|
294
|
-
}.to raise_error(/cannot transfer nested constant.*V/i)
|
295
|
-
|
296
|
-
expect(TestClass).to be(original_tc)
|
297
|
-
end
|
286
|
+
expect(TestClass).to be(original_tc)
|
298
287
|
end
|
299
288
|
|
300
|
-
|
301
|
-
|
302
|
-
|
289
|
+
it 'raises an error if asked to transfer nested constants on a constant that does not support nested constants' do
|
290
|
+
stub = Module.new
|
291
|
+
expect {
|
292
|
+
stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => true)
|
293
|
+
}.to raise_error(ArgumentError)
|
303
294
|
|
304
|
-
|
305
|
-
it_behaves_like 'loaded constant stubbing', "::TestClass"
|
306
|
-
end
|
295
|
+
expect(TOP_LEVEL_VALUE_CONST).to eq(7)
|
307
296
|
|
308
|
-
|
309
|
-
|
310
|
-
|
297
|
+
expect {
|
298
|
+
stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => [:M])
|
299
|
+
}.to raise_error(ArgumentError)
|
311
300
|
|
312
|
-
|
313
|
-
it_behaves_like "unloaded constant stubbing", "TestClass::Hash"
|
301
|
+
expect(TOP_LEVEL_VALUE_CONST).to eq(7)
|
314
302
|
end
|
315
303
|
|
316
|
-
|
317
|
-
|
304
|
+
it 'raises an error if asked to transfer a nested constant that is not defined' do
|
305
|
+
original_tc = TestClass
|
306
|
+
expect(defined?(TestClass::V)).to be_false
|
307
|
+
stub = Module.new
|
308
|
+
|
309
|
+
expect {
|
310
|
+
stub_const("TestClass", stub, :transfer_nested_constants => [:V])
|
311
|
+
}.to raise_error(/cannot transfer nested constant.*V/i)
|
312
|
+
|
313
|
+
expect(TestClass).to be(original_tc)
|
318
314
|
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context 'for a loaded nested constant' do
|
318
|
+
it_behaves_like "loaded constant stubbing", "TestClass::Nested"
|
319
|
+
end
|
320
|
+
|
321
|
+
context 'for a loaded constant prefixed with ::' do
|
322
|
+
it_behaves_like 'loaded constant stubbing', "::TestClass"
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'for an unloaded constant prefixed with ::' do
|
326
|
+
it_behaves_like 'unloaded constant stubbing', "::SomeUndefinedConst"
|
327
|
+
end
|
319
328
|
|
320
|
-
|
321
|
-
|
329
|
+
context 'for an unloaded constant with nested name that matches a top-level constant' do
|
330
|
+
it_behaves_like "unloaded constant stubbing", "TestClass::Hash"
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'for a loaded deeply nested constant' do
|
334
|
+
it_behaves_like "loaded constant stubbing", "TestClass::Nested::NestedEvenMore"
|
335
|
+
end
|
336
|
+
|
337
|
+
context 'for an unloaded unnested constant' do
|
338
|
+
it_behaves_like "unloaded constant stubbing", "X"
|
339
|
+
end
|
340
|
+
|
341
|
+
context 'for an unloaded nested constant' do
|
342
|
+
it_behaves_like "unloaded constant stubbing", "X::Y"
|
343
|
+
|
344
|
+
it 'removes the root constant when rspec clears its mocks' do
|
345
|
+
expect(defined?(X)).to be_false
|
346
|
+
stub_const("X::Y", 7)
|
347
|
+
reset_rspec_mocks
|
348
|
+
expect(defined?(X)).to be_false
|
322
349
|
end
|
350
|
+
end
|
323
351
|
|
324
|
-
|
325
|
-
|
352
|
+
context 'for an unloaded deeply nested constant' do
|
353
|
+
it_behaves_like "unloaded constant stubbing", "X::Y::Z"
|
326
354
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
end
|
355
|
+
it 'removes the root constant when rspec clears its mocks' do
|
356
|
+
expect(defined?(X)).to be_false
|
357
|
+
stub_const("X::Y::Z", 7)
|
358
|
+
reset_rspec_mocks
|
359
|
+
expect(defined?(X)).to be_false
|
333
360
|
end
|
361
|
+
end
|
334
362
|
|
335
|
-
|
336
|
-
|
363
|
+
context 'for an unloaded constant nested within a loaded constant' do
|
364
|
+
it_behaves_like "unloaded constant stubbing", "TestClass::X"
|
337
365
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
366
|
+
it 'removes the unloaded constant but leaves the loaded constant when rspec resets its mocks' do
|
367
|
+
expect(defined?(TestClass)).to be_true
|
368
|
+
expect(defined?(TestClass::X)).to be_false
|
369
|
+
stub_const("TestClass::X", 7)
|
370
|
+
reset_rspec_mocks
|
371
|
+
expect(defined?(TestClass)).to be_true
|
372
|
+
expect(defined?(TestClass::X)).to be_false
|
344
373
|
end
|
345
374
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
it 'removes the unloaded constant but leaves the loaded constant when rspec resets its mocks' do
|
350
|
-
expect(defined?(TestClass)).to be_true
|
351
|
-
expect(defined?(TestClass::X)).to be_false
|
352
|
-
stub_const("TestClass::X", 7)
|
353
|
-
reset_rspec_mocks
|
354
|
-
expect(defined?(TestClass)).to be_true
|
355
|
-
expect(defined?(TestClass::X)).to be_false
|
356
|
-
end
|
357
|
-
|
358
|
-
it 'raises a helpful error if it cannot be stubbed due to an intermediary constant that is not a module' do
|
359
|
-
expect(TestClass::M).to be_a(Symbol)
|
360
|
-
expect { stub_const("TestClass::M::X", 5) }.to raise_error(/cannot stub/i)
|
361
|
-
end
|
375
|
+
it 'raises a helpful error if it cannot be stubbed due to an intermediary constant that is not a module' do
|
376
|
+
expect(TestClass::M).to be_a(Symbol)
|
377
|
+
expect { stub_const("TestClass::M::X", 5) }.to raise_error(/cannot stub/i)
|
362
378
|
end
|
379
|
+
end
|
380
|
+
|
381
|
+
context 'for an unloaded constant nested deeply within a deeply nested loaded constant' do
|
382
|
+
it_behaves_like "unloaded constant stubbing", "TestClass::Nested::NestedEvenMore::X::Y::Z"
|
363
383
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
reset_rspec_mocks
|
372
|
-
expect(defined?(TestClass::Nested::NestedEvenMore)).to be_true
|
373
|
-
expect(defined?(TestClass::Nested::NestedEvenMore::X)).to be_false
|
374
|
-
end
|
384
|
+
it 'removes the first unloaded constant but leaves the loaded nested constant when rspec resets its mocks' do
|
385
|
+
expect(defined?(TestClass::Nested::NestedEvenMore)).to be_true
|
386
|
+
expect(defined?(TestClass::Nested::NestedEvenMore::X)).to be_false
|
387
|
+
stub_const("TestClass::Nested::NestedEvenMore::X::Y::Z", 7)
|
388
|
+
reset_rspec_mocks
|
389
|
+
expect(defined?(TestClass::Nested::NestedEvenMore)).to be_true
|
390
|
+
expect(defined?(TestClass::Nested::NestedEvenMore::X)).to be_false
|
375
391
|
end
|
376
392
|
end
|
377
393
|
end
|
394
|
+
end
|
378
395
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
let(:const) { Constant.original("TestClass::M") }
|
383
|
-
|
384
|
-
it("exposes its name") { expect(const.name).to eq("TestClass::M") }
|
385
|
-
it("indicates it was previously defined") { expect(const).to be_previously_defined }
|
386
|
-
it("indicates it has not been mutated") { expect(const).not_to be_mutated }
|
387
|
-
it("indicates it has not been stubbed") { expect(const).not_to be_stubbed }
|
388
|
-
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
389
|
-
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
390
|
-
end
|
396
|
+
describe Constant do
|
397
|
+
require "rspec/mocks/mutate_const"
|
398
|
+
include RSpec::Mocks::RecursiveConstMethods
|
391
399
|
|
392
|
-
|
393
|
-
|
394
|
-
|
400
|
+
def reset_rspec_mocks
|
401
|
+
Spy.teardown
|
402
|
+
end
|
395
403
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
401
|
-
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
402
|
-
end
|
404
|
+
def on_const(const_name)
|
405
|
+
parent_const = recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
|
406
|
+
Spy.on_const(parent_const, const_name.split('::').last.to_sym)
|
407
|
+
end
|
403
408
|
|
404
|
-
|
405
|
-
|
406
|
-
|
409
|
+
def stub_const(const_name, value)
|
410
|
+
on_const(const_name).and_return(value)
|
411
|
+
end
|
407
412
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
it("indicates it has been stubbed") { expect(const).to be_stubbed }
|
412
|
-
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
413
|
-
it("returns nil for the original value") { expect(const.original_value).to be_nil }
|
414
|
-
end
|
413
|
+
def hide_const(const_name)
|
414
|
+
on_const(const_name).and_hide
|
415
|
+
end
|
415
416
|
|
416
|
-
|
417
|
-
|
417
|
+
def original(const_name)
|
418
|
+
parent_const = recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
|
419
|
+
Spy.get_const(parent_const, const_name.split('::').last.to_sym)
|
420
|
+
end
|
418
421
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
it("indicates it has not been stubbed") { expect(const).not_to be_stubbed }
|
423
|
-
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
424
|
-
it("returns nil for the original value") { expect(const.original_value).to be_nil }
|
425
|
-
end
|
422
|
+
describe ".original" do
|
423
|
+
context 'for a previously defined unstubbed constant' do
|
424
|
+
let(:const) { original("TestClass::M") }
|
426
425
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
it("indicates it has been mutated") { expect(const).to be_mutated }
|
435
|
-
it("indicates it has been stubbed") { expect(const).to be_stubbed }
|
436
|
-
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
437
|
-
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
438
|
-
end
|
426
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::M") }
|
427
|
+
it("indicates it was previously defined") { expect(const).to be_previously_defined }
|
428
|
+
it("indicates it has not been mutated") { expect(const).not_to be_mutated }
|
429
|
+
it("indicates it has not been stubbed") { expect(const).not_to be_stubbed }
|
430
|
+
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
431
|
+
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
432
|
+
end
|
439
433
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
let(:const) { Constant.original("TestClass::Undefined") }
|
444
|
-
|
445
|
-
it("exposes its name") { expect(const.name).to eq("TestClass::Undefined") }
|
446
|
-
it("indicates it was not previously defined") { expect(const).not_to be_previously_defined }
|
447
|
-
it("indicates it has been mutated") { expect(const).to be_mutated }
|
448
|
-
it("indicates it has been stubbed") { expect(const).to be_stubbed }
|
449
|
-
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
450
|
-
it("returns nil for the original value") { expect(const.original_value).to be_nil }
|
451
|
-
end
|
434
|
+
context 'for a previously defined stubbed constant' do
|
435
|
+
before { stub_const("TestClass::M", :other) }
|
436
|
+
let(:const) { original("TestClass::M") }
|
452
437
|
|
453
|
-
|
454
|
-
|
455
|
-
|
438
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::M") }
|
439
|
+
it("indicates it was previously defined") { expect(const).to be_previously_defined }
|
440
|
+
it("indicates it has been mutated") { expect(const).to be_mutated }
|
441
|
+
it("indicates it has been stubbed") { expect(const).to be_stubbed }
|
442
|
+
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
443
|
+
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
444
|
+
end
|
456
445
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
it("indicates it has not been stubbed") { expect(const).not_to be_stubbed }
|
461
|
-
it("indicates it has been hidden") { expect(const).to be_hidden }
|
462
|
-
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
463
|
-
end
|
446
|
+
context 'for a previously undefined stubbed constant' do
|
447
|
+
before { stub_const("TestClass::Undefined", :other) }
|
448
|
+
let(:const) { original("TestClass::Undefined") }
|
464
449
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
450
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::Undefined") }
|
451
|
+
it("indicates it was not previously defined") { expect(const).not_to be_previously_defined }
|
452
|
+
it("indicates it has been mutated") { expect(const).to be_mutated }
|
453
|
+
it("indicates it has been stubbed") { expect(const).to be_stubbed }
|
454
|
+
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
455
|
+
it("returns nil for the original value") { expect(const.original_value).to be_nil }
|
456
|
+
end
|
457
|
+
|
458
|
+
context 'for a previously undefined unstubbed constant' do
|
459
|
+
let(:const) { original("TestClass::Undefined") }
|
460
|
+
|
461
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::Undefined") }
|
462
|
+
it("indicates it was not previously defined") { expect(const).not_to be_previously_defined }
|
463
|
+
it("indicates it has not been mutated") { expect(const).not_to be_mutated }
|
464
|
+
it("indicates it has not been stubbed") { expect(const).not_to be_stubbed }
|
465
|
+
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
466
|
+
it("returns nil for the original value") { expect(const.original_value).to be_nil }
|
467
|
+
end
|
468
|
+
|
469
|
+
context 'for a previously defined constant that has been stubbed twice' do
|
470
|
+
before { stub_const("TestClass::M", 1) }
|
471
|
+
before { stub_const("TestClass::M", 2) }
|
472
|
+
let(:const) { original("TestClass::M") }
|
473
|
+
|
474
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::M") }
|
475
|
+
it("indicates it was previously defined") { expect(const).to be_previously_defined }
|
476
|
+
it("indicates it has been mutated") { expect(const).to be_mutated }
|
477
|
+
it("indicates it has been stubbed") { expect(const).to be_stubbed }
|
478
|
+
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
479
|
+
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
480
|
+
end
|
481
|
+
|
482
|
+
context 'for a previously undefined constant that has been stubbed twice' do
|
483
|
+
before { stub_const("TestClass::Undefined", 1) }
|
484
|
+
before { stub_const("TestClass::Undefined", 2) }
|
485
|
+
let(:const) { original("TestClass::Undefined") }
|
486
|
+
|
487
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::Undefined") }
|
488
|
+
it("indicates it was not previously defined") { expect(const).not_to be_previously_defined }
|
489
|
+
it("indicates it has been mutated") { expect(const).to be_mutated }
|
490
|
+
it("indicates it has been stubbed") { expect(const).to be_stubbed }
|
491
|
+
it("indicates it has not been hidden") { expect(const).not_to be_hidden }
|
492
|
+
it("returns nil for the original value") { expect(const.original_value).to be_nil }
|
493
|
+
end
|
494
|
+
|
495
|
+
context 'for a previously defined hidden constant' do
|
496
|
+
before { hide_const("TestClass::M") }
|
497
|
+
let(:const) { original("TestClass::M") }
|
498
|
+
|
499
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::M") }
|
500
|
+
it("indicates it was previously defined") { expect(const).to be_previously_defined }
|
501
|
+
it("indicates it has been mutated") { expect(const).to be_mutated }
|
502
|
+
it("indicates it has not been stubbed") { expect(const).not_to be_stubbed }
|
503
|
+
it("indicates it has been hidden") { expect(const).to be_hidden }
|
504
|
+
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
505
|
+
end
|
506
|
+
|
507
|
+
context 'for a previously defined constant that has been hidden twice' do
|
508
|
+
before { hide_const("TestClass::M") }
|
509
|
+
before { hide_const("TestClass::M") }
|
510
|
+
let(:const) { original("TestClass::M") }
|
511
|
+
|
512
|
+
it("exposes its name") { expect(const.name).to eq("TestClass::M") }
|
513
|
+
it("indicates it was previously defined") { expect(const).to be_previously_defined }
|
514
|
+
it("indicates it has been mutated") { expect(const).to be_mutated }
|
515
|
+
it("indicates it has not been stubbed") { expect(const).not_to be_stubbed }
|
516
|
+
it("indicates it has been hidden") { expect(const).to be_hidden }
|
517
|
+
it("exposes its original value") { expect(const.original_value).to eq(:m) }
|
477
518
|
end
|
478
519
|
end
|
479
520
|
end
|
480
521
|
end
|
481
|
-
|