rspec-fire 0.2.2 → 0.3.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/HISTORY +6 -3
- data/README.md +81 -0
- data/lib/rspec/fire.rb +199 -31
- data/rspec-fire.gemspec +1 -1
- data/spec/fire_double_spec.rb +308 -0
- metadata +6 -6
data/HISTORY
CHANGED
@@ -4,8 +4,11 @@
|
|
4
4
|
0.2 - 3 September 2011
|
5
5
|
* Checks method arity when a 'with' call is present
|
6
6
|
|
7
|
-
0.2.1 - 14 January
|
7
|
+
0.2.1 - 14 January 2012
|
8
8
|
* rspec 2.8 support
|
9
9
|
|
10
|
-
0.2.2 - 31 January
|
11
|
-
* Added missing require needed for some code bases.
|
10
|
+
0.2.2 - 31 January 2012
|
11
|
+
* Added missing require needed for some code bases.
|
12
|
+
|
13
|
+
0.3.0 - 17 March 2012
|
14
|
+
* Added support for doubling constants.
|
data/README.md
CHANGED
@@ -63,6 +63,12 @@ Bit of setup in your `spec_helper.rb`:
|
|
63
63
|
|
64
64
|
Specify the class being doubled in your specs:
|
65
65
|
|
66
|
+
class User < Struct.new(:notifier)
|
67
|
+
def suspend!
|
68
|
+
notifier.notify("suspended as")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
66
72
|
describe User, '#suspend!' do
|
67
73
|
it 'sends a notification' do
|
68
74
|
# Only this one line differs from how you write specs normally
|
@@ -97,6 +103,81 @@ the context of your app:
|
|
97
103
|
|
98
104
|
rspec -rspec/spec_helper.rb spec/unit/my_spec.rb
|
99
105
|
|
106
|
+
### Doubling constants
|
107
|
+
|
108
|
+
A particularly excellent feature. You can stub out constants using
|
109
|
+
`fire_replaced_class_double`, removing the need to dependency inject
|
110
|
+
collaborators (a technique that can sometimes be cumbersome).
|
111
|
+
|
112
|
+
class User
|
113
|
+
def suspend!
|
114
|
+
EmailNotifier.notify("suspended as")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe User, '#suspend!' do
|
119
|
+
it 'sends a notification' do
|
120
|
+
# Only this one line differs from how you write specs normally
|
121
|
+
notifier = fire_replaced_class_double("EmailNotifier")
|
122
|
+
|
123
|
+
# Alternately, you can use this fluent interface
|
124
|
+
notifier = fire_class_double("EmailNotifier").as_replaced_constant
|
125
|
+
|
126
|
+
notifier.should_receive(:notify).with("suspended as")
|
127
|
+
|
128
|
+
user = User.new
|
129
|
+
user.suspend!
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
This will probably become the default behaviour once we figure out a better
|
134
|
+
name for it.
|
135
|
+
|
136
|
+
### Stubbing Constants
|
137
|
+
|
138
|
+
The constant stubbing logic used when doubling class constants can be
|
139
|
+
used for any constant.
|
140
|
+
|
141
|
+
class MapReduceRunner
|
142
|
+
ITEMS_PER_BATCH = 1000
|
143
|
+
end
|
144
|
+
|
145
|
+
describe MapReduceRunner, "when it has too many items for one batch" do
|
146
|
+
it "breaks the items up into smaller batches" do
|
147
|
+
# the test would be really slow if we had to make more than 1000 items,
|
148
|
+
# so let's change the threshold for this one test.
|
149
|
+
stub_const("MapReduceRunner::ITEMS_PER_BATCH", 10)
|
150
|
+
|
151
|
+
MapReduceRunner.run_with(twenty_items)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
### Transferring nested constants to doubled constants
|
156
|
+
|
157
|
+
When you use `fire_replaced_class_double` to replace a class or module
|
158
|
+
that also acts as a namespace for other classes and constants, your
|
159
|
+
access to these constants is cut off for the duration of the example
|
160
|
+
(since the doubled constant does not automatically have all of the
|
161
|
+
nested constants). The `:transfer_nested_constants` option is provided
|
162
|
+
to deal with this:
|
163
|
+
|
164
|
+
module MyCoolGem
|
165
|
+
class Widget
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# once you do this, you can no longer access MyCoolGem::Widget in your example...
|
170
|
+
fire_replaced_class_double("MyCoolGem")
|
171
|
+
|
172
|
+
# ...unless you tell rspec-fire to transfer all nested constants
|
173
|
+
fire_class_double("MyCoolGem").as_replaced_constant(:transfer_nested_constants => true)
|
174
|
+
|
175
|
+
# ...or give it a list of constants to transfer
|
176
|
+
fire_class_double("MyCoolGem").as_replaced_constant(:transfer_nested_constants => [:Widget])
|
177
|
+
|
178
|
+
# You can also use this when using #stub_const directly
|
179
|
+
stub_const("MyCoolGem", :transfer_nested_constants => true)
|
180
|
+
|
100
181
|
### Doubling class methods
|
101
182
|
|
102
183
|
Particularly handy for `ActiveRecord` finders. Use `fire_class_double`. If you
|
data/lib/rspec/fire.rb
CHANGED
@@ -5,11 +5,11 @@ require 'delegate'
|
|
5
5
|
module RSpec
|
6
6
|
module Fire
|
7
7
|
module RecursiveConstMethods
|
8
|
-
def recursive_const_get
|
8
|
+
def recursive_const_get name
|
9
9
|
name.split('::').inject(Object) {|klass,name| klass.const_get name }
|
10
10
|
end
|
11
11
|
|
12
|
-
def recursive_const_defined?
|
12
|
+
def recursive_const_defined? name
|
13
13
|
!!name.split('::').inject(Object) {|klass,name|
|
14
14
|
if klass && klass.const_defined?(name)
|
15
15
|
klass.const_get name
|
@@ -24,10 +24,11 @@ module RSpec
|
|
24
24
|
|
25
25
|
AM = RSpec::Mocks::ArgumentMatchers
|
26
26
|
|
27
|
-
def initialize(
|
28
|
-
@
|
27
|
+
def initialize(double, method_finder, backing)
|
28
|
+
@double = double
|
29
29
|
@method_finder = method_finder
|
30
30
|
@backing = backing
|
31
|
+
@sym = backing.respond_to?(:sym) ? @backing.sym : @backing.message
|
31
32
|
super(backing)
|
32
33
|
end
|
33
34
|
|
@@ -50,10 +51,8 @@ module RSpec
|
|
50
51
|
protected
|
51
52
|
|
52
53
|
def ensure_arity(actual)
|
53
|
-
|
54
|
-
|
55
|
-
send(@method_finder, sym).
|
56
|
-
should have_arity(actual)
|
54
|
+
@double.with_doubled_class do |klass|
|
55
|
+
klass.send(@method_finder, @sym).should have_arity(actual)
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
@@ -69,23 +68,13 @@ module RSpec
|
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
72
|
-
|
71
|
+
module FireDoublable
|
73
72
|
extend RSpec::Matchers::DSL
|
74
73
|
include RecursiveConstMethods
|
75
74
|
|
76
|
-
def initialize(doubled_class, *args)
|
77
|
-
args << {} unless Hash === args.last
|
78
|
-
|
79
|
-
@__doubled_class_name = doubled_class
|
80
|
-
|
81
|
-
# __declared_as copied from rspec/mocks definition of `double`
|
82
|
-
args.last[:__declared_as] = 'FireDouble'
|
83
|
-
super(doubled_class, *args)
|
84
|
-
end
|
85
|
-
|
86
75
|
def should_receive(method_name)
|
87
76
|
ensure_implemented(method_name)
|
88
|
-
ShouldProxy.new(
|
77
|
+
ShouldProxy.new(self, @__method_finder, super)
|
89
78
|
end
|
90
79
|
|
91
80
|
def should_not_receive(method_name)
|
@@ -98,12 +87,17 @@ module RSpec
|
|
98
87
|
super
|
99
88
|
end
|
100
89
|
|
90
|
+
def with_doubled_class
|
91
|
+
if recursive_const_defined?(@__doubled_class_name)
|
92
|
+
yield recursive_const_get(@__doubled_class_name)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
101
96
|
protected
|
102
97
|
|
103
98
|
def ensure_implemented(*method_names)
|
104
|
-
|
105
|
-
|
106
|
-
should implement(method_names, @__checked_methods)
|
99
|
+
with_doubled_class do |klass|
|
100
|
+
klass.should implement(method_names, @__checked_methods)
|
107
101
|
end
|
108
102
|
end
|
109
103
|
|
@@ -131,20 +125,190 @@ module RSpec
|
|
131
125
|
end
|
132
126
|
end
|
133
127
|
|
134
|
-
class FireObjectDouble <
|
135
|
-
|
128
|
+
class FireObjectDouble < RSpec::Mocks::Mock
|
129
|
+
include FireDoublable
|
130
|
+
|
131
|
+
def initialize(doubled_class, *args)
|
132
|
+
args << {} unless Hash === args.last
|
133
|
+
|
134
|
+
@__doubled_class_name = doubled_class
|
135
|
+
|
136
|
+
# __declared_as copied from rspec/mocks definition of `double`
|
137
|
+
args.last[:__declared_as] = 'FireDouble'
|
136
138
|
super
|
137
139
|
@__checked_methods = :public_instance_methods
|
138
140
|
@__method_finder = :instance_method
|
139
141
|
end
|
140
142
|
end
|
141
143
|
|
142
|
-
class
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
class FireClassDoubleBuilder
|
145
|
+
def self.build(doubled_class, stubs = {})
|
146
|
+
Module.new do
|
147
|
+
extend FireDoublable
|
148
|
+
|
149
|
+
@__doubled_class_name = doubled_class
|
150
|
+
@__checked_methods = :public_methods
|
151
|
+
@__method_finder = :method
|
152
|
+
|
153
|
+
stubs.each do |message, response|
|
154
|
+
stub(message).and_return(response)
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.as_replaced_constant(options = {})
|
158
|
+
@__original_class = ConstantStubber.stub!(@__doubled_class_name, self, options)
|
159
|
+
extend AsReplacedConstant
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.to_s
|
164
|
+
@__doubled_class_name + " (fire double)"
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.inspect
|
168
|
+
to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.name
|
172
|
+
@__doubled_class_name
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.method_missing(name, *args)
|
176
|
+
__mock_proxy.raise_unexpected_message_error(name, *args)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
module AsReplacedConstant
|
182
|
+
def with_doubled_class
|
183
|
+
yield @__original_class if @__original_class
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class ConstantStubber
|
189
|
+
extend RecursiveConstMethods
|
190
|
+
|
191
|
+
class DefinedConstantReplacer
|
192
|
+
include RecursiveConstMethods
|
193
|
+
attr_reader :original_value
|
194
|
+
|
195
|
+
def initialize(full_constant_name, stubbed_value, transfer_nested_constants)
|
196
|
+
@full_constant_name = full_constant_name
|
197
|
+
@stubbed_value = stubbed_value
|
198
|
+
@transfer_nested_constants = transfer_nested_constants
|
199
|
+
end
|
200
|
+
|
201
|
+
def stub!
|
202
|
+
context_parts = @full_constant_name.split('::')
|
203
|
+
@const_name = context_parts.pop
|
204
|
+
@context = recursive_const_get(context_parts.join('::'))
|
205
|
+
@original_value = @context.const_get(@const_name)
|
206
|
+
|
207
|
+
constants_to_transfer = verify_constants_to_transfer!
|
208
|
+
|
209
|
+
@context.send(:remove_const, @const_name)
|
210
|
+
@context.const_set(@const_name, @stubbed_value)
|
211
|
+
|
212
|
+
transfer_nested_constants(constants_to_transfer)
|
213
|
+
end
|
214
|
+
|
215
|
+
def rspec_reset
|
216
|
+
if recursive_const_get(@full_constant_name).equal?(@stubbed_value)
|
217
|
+
@context.send(:remove_const, @const_name)
|
218
|
+
@context.const_set(@const_name, @original_value)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def transfer_nested_constants(constants)
|
223
|
+
constants.each do |const|
|
224
|
+
@stubbed_value.const_set(const, original_value.const_get(const))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def verify_constants_to_transfer!
|
229
|
+
return [] unless @transfer_nested_constants
|
230
|
+
|
231
|
+
{ @original_value => "the original value", @stubbed_value => "the stubbed value" }.each do |value, description|
|
232
|
+
unless value.respond_to?(:constants)
|
233
|
+
raise ArgumentError,
|
234
|
+
"Cannot transfer nested constants for #{@full_constant_name} " +
|
235
|
+
"since #{description} is not a class or module and only classes " +
|
236
|
+
"and modules support nested constants."
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
if @transfer_nested_constants.is_a?(Array)
|
241
|
+
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
|
242
|
+
undefined_constants = @transfer_nested_constants - @original_value.constants
|
243
|
+
|
244
|
+
if undefined_constants.any?
|
245
|
+
available_constants = @original_value.constants - @transfer_nested_constants
|
246
|
+
raise ArgumentError,
|
247
|
+
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
|
248
|
+
"for #{@full_constant_name} since they are not defined. Did you mean " +
|
249
|
+
"#{available_constants.join(' or ')}?"
|
250
|
+
end
|
251
|
+
|
252
|
+
@transfer_nested_constants
|
253
|
+
else
|
254
|
+
@original_value.constants
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class UndefinedConstantSetter
|
260
|
+
include RecursiveConstMethods
|
261
|
+
|
262
|
+
def initialize(full_constant_name, stubbed_value)
|
263
|
+
@full_constant_name = full_constant_name
|
264
|
+
@stubbed_value = stubbed_value
|
265
|
+
end
|
266
|
+
|
267
|
+
def original_value
|
268
|
+
# always nil
|
269
|
+
end
|
270
|
+
|
271
|
+
def stub!
|
272
|
+
context_parts = @full_constant_name.split('::')
|
273
|
+
const_name = context_parts.pop
|
274
|
+
|
275
|
+
remaining_parts = context_parts.dup
|
276
|
+
@deepest_defined_const = context_parts.inject(Object) do |klass, name|
|
277
|
+
break klass unless klass.const_defined?(name)
|
278
|
+
remaining_parts.shift
|
279
|
+
klass.const_get(name)
|
280
|
+
end
|
281
|
+
|
282
|
+
context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
|
283
|
+
klass.const_set(name, Module.new)
|
284
|
+
end
|
285
|
+
|
286
|
+
@const_to_remove = remaining_parts.first || const_name
|
287
|
+
context.const_set(const_name, @stubbed_value)
|
288
|
+
end
|
289
|
+
|
290
|
+
def rspec_reset
|
291
|
+
if recursive_const_get(@full_constant_name).equal?(@stubbed_value)
|
292
|
+
@deepest_defined_const.send(:remove_const, @const_to_remove)
|
293
|
+
end
|
294
|
+
end
|
147
295
|
end
|
296
|
+
|
297
|
+
def self.stub!(constant_name, value, options = {})
|
298
|
+
stubber = if recursive_const_defined?(constant_name)
|
299
|
+
DefinedConstantReplacer.new(constant_name, value, options[:transfer_nested_constants])
|
300
|
+
else
|
301
|
+
UndefinedConstantSetter.new(constant_name, value)
|
302
|
+
end
|
303
|
+
|
304
|
+
stubber.stub!
|
305
|
+
::RSpec::Mocks.space.add(stubber)
|
306
|
+
stubber.original_value
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def stub_const(name, value, options = {})
|
311
|
+
ConstantStubber.stub!(name, value, options)
|
148
312
|
end
|
149
313
|
|
150
314
|
def fire_double(*args)
|
@@ -152,7 +316,11 @@ module RSpec
|
|
152
316
|
end
|
153
317
|
|
154
318
|
def fire_class_double(*args)
|
155
|
-
|
319
|
+
FireClassDoubleBuilder.build(*args)
|
320
|
+
end
|
321
|
+
|
322
|
+
def fire_replaced_class_double(*args)
|
323
|
+
fire_class_double(*args).as_replaced_constant
|
156
324
|
end
|
157
325
|
end
|
158
326
|
end
|
data/rspec-fire.gemspec
CHANGED
data/spec/fire_double_spec.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
TOP_LEVEL_VALUE_CONST = 7
|
4
|
+
|
5
|
+
RSpec::Mocks::Space.class_eval do
|
6
|
+
# Deal with the fact that #mocks was renamed to #receivers for RSpec 2.9:
|
7
|
+
# https://github.com/rspec/rspec-mocks/commit/17c259ea5143d309e90ca6d53d40f6356ac2d0a5
|
8
|
+
unless private_instance_methods.map(&:to_sym).include?(:receivers)
|
9
|
+
alias_method :receivers, :mocks
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
3
13
|
module TestMethods
|
4
14
|
def defined_method
|
5
15
|
raise "Y U NO MOCK?"
|
@@ -16,6 +26,14 @@ end
|
|
16
26
|
|
17
27
|
class TestClass
|
18
28
|
extend TestMethods
|
29
|
+
|
30
|
+
M = :m
|
31
|
+
N = :n
|
32
|
+
|
33
|
+
class Nested
|
34
|
+
class NestedEvenMore
|
35
|
+
end
|
36
|
+
end
|
19
37
|
end
|
20
38
|
|
21
39
|
shared_examples_for 'a fire-enhanced double method' do
|
@@ -134,4 +152,294 @@ describe '#fire_class_double' do
|
|
134
152
|
let(:doubled_object) { fire_class_double("TestClass") }
|
135
153
|
|
136
154
|
it_should_behave_like 'a fire-enhanced double'
|
155
|
+
|
156
|
+
it 'uses a module for the doubled object so that it supports nested constants like a real class' do
|
157
|
+
doubled_object.should be_a(Module)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'has a readable string representation' do
|
161
|
+
doubled_object.to_s.should include("TestClass (fire double)")
|
162
|
+
doubled_object.inspect.should include("TestClass (fire double)")
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'assigns the class name' do
|
166
|
+
TestClass.name.should eq("TestClass")
|
167
|
+
doubled_object.name.should eq("TestClass")
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'raises a mock expectation error for undefind methods' do
|
171
|
+
expect {
|
172
|
+
doubled_object.abc
|
173
|
+
}.to raise_error(RSpec::Mocks::MockExpectationError)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'allows stubs to be specified as a hash' do
|
177
|
+
double = fire_class_double("SomeClass", :a => 5, :b => 8)
|
178
|
+
double.a.should eq(5)
|
179
|
+
double.b.should eq(8)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def reset_rspec_mocks
|
184
|
+
::RSpec::Mocks.space.reset_all
|
185
|
+
end
|
186
|
+
|
187
|
+
describe '#fire_replaced_class_double (for an existing class)' do
|
188
|
+
let(:doubled_object) { fire_replaced_class_double("TestClass") }
|
189
|
+
|
190
|
+
it_should_behave_like 'a fire-enhanced double'
|
191
|
+
|
192
|
+
it 'replaces the constant for the duration of the test' do
|
193
|
+
orig_class = TestClass
|
194
|
+
doubled_object.should_not be(orig_class)
|
195
|
+
TestClass.should be(doubled_object)
|
196
|
+
reset_rspec_mocks
|
197
|
+
TestClass.should be(orig_class)
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'supports transferring nested constants to the double' do
|
201
|
+
fire_class_double("TestClass").as_replaced_constant(:transfer_nested_constants => true)
|
202
|
+
TestClass::M.should eq(:m)
|
203
|
+
TestClass::N.should eq(:n)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe '#fire_replaced_class_double (for a non-existant class)' do
|
208
|
+
it 'allows any method to be mocked' do
|
209
|
+
double = fire_replaced_class_double("A::B::C")
|
210
|
+
double.should_receive(:foo).with("a").and_return(:bar)
|
211
|
+
A::B::C.foo("a").should eq(:bar)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
shared_examples_for "loaded constant stubbing" do |const_name|
|
216
|
+
include RSpec::Fire::RecursiveConstMethods
|
217
|
+
let!(:original_const_value) { const }
|
218
|
+
after { change_const_value_to(original_const_value) }
|
219
|
+
|
220
|
+
define_method :const do
|
221
|
+
recursive_const_get(const_name)
|
222
|
+
end
|
223
|
+
|
224
|
+
define_method :parent_const do
|
225
|
+
recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
|
226
|
+
end
|
227
|
+
|
228
|
+
define_method :last_const_part do
|
229
|
+
const_name.split('::').last
|
230
|
+
end
|
231
|
+
|
232
|
+
def change_const_value_to(value)
|
233
|
+
parent_const.send(:remove_const, last_const_part)
|
234
|
+
parent_const.const_set(last_const_part, value)
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'allows it to be stubbed' do
|
238
|
+
const.should_not eq(7)
|
239
|
+
stub_const(const_name, 7)
|
240
|
+
const.should eq(7)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'resets it to its original value when rspec clears its mocks' do
|
244
|
+
original_value = const
|
245
|
+
original_value.should_not eq(:a)
|
246
|
+
stub_const(const_name, :a)
|
247
|
+
reset_rspec_mocks
|
248
|
+
const.should be(original_value)
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'does not reset the value to its original value when rspec clears its mocks if the example modifies the value of the constant' do
|
252
|
+
stub_const(const_name, :a)
|
253
|
+
change_const_value_to(new_const_value = Object.new)
|
254
|
+
reset_rspec_mocks
|
255
|
+
const.should be(new_const_value)
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'returns the original value' do
|
259
|
+
orig_value = const
|
260
|
+
returned_value = stub_const(const_name, 7)
|
261
|
+
returned_value.should be(orig_value)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
shared_examples_for "unloaded constant stubbing" do |const_name|
|
266
|
+
include RSpec::Fire::RecursiveConstMethods
|
267
|
+
before { recursive_const_defined?(const_name).should be_false }
|
268
|
+
|
269
|
+
define_method :const do
|
270
|
+
recursive_const_get(const_name)
|
271
|
+
end
|
272
|
+
|
273
|
+
define_method :parent_const do
|
274
|
+
recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, ''))
|
275
|
+
end
|
276
|
+
|
277
|
+
define_method :last_const_part do
|
278
|
+
const_name.split('::').last
|
279
|
+
end
|
280
|
+
|
281
|
+
def change_const_value_to(value)
|
282
|
+
parent_const.send(:remove_const, last_const_part)
|
283
|
+
parent_const.const_set(last_const_part, value)
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'allows it to be stubbed' do
|
287
|
+
stub_const(const_name, 7)
|
288
|
+
const.should eq(7)
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'removes the constant when rspec clears its mocks' do
|
292
|
+
stub_const(const_name, 7)
|
293
|
+
reset_rspec_mocks
|
294
|
+
recursive_const_defined?(const_name).should be_false
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'does not remove the constant when the example manually sets it' do
|
298
|
+
begin
|
299
|
+
stub_const(const_name, 7)
|
300
|
+
stubber = RSpec::Mocks.space.send(:receivers).first
|
301
|
+
change_const_value_to(new_const_value = Object.new)
|
302
|
+
reset_rspec_mocks
|
303
|
+
const.should equal(new_const_value)
|
304
|
+
ensure
|
305
|
+
change_const_value_to(7)
|
306
|
+
stubber.rspec_reset
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'returns nil since it was not originally set' do
|
311
|
+
stub_const(const_name, 7).should be_nil
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'ignores the :transfer_nested_constants if passed' do
|
315
|
+
stub = Module.new
|
316
|
+
stub_const(const_name, stub, :transfer_nested_constants => true)
|
317
|
+
stub.constants.should eq([])
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
describe "#stub_const" do
|
322
|
+
context 'for a loaded unnested constant' do
|
323
|
+
it_behaves_like "loaded constant stubbing", "TestClass"
|
324
|
+
|
325
|
+
it 'allows nested constants to be transferred to a stub module' do
|
326
|
+
tc_nested = TestClass::Nested
|
327
|
+
stub = Module.new
|
328
|
+
stub_const("TestClass", stub, :transfer_nested_constants => true)
|
329
|
+
stub::M.should eq(:m)
|
330
|
+
stub::N.should eq(:n)
|
331
|
+
stub::Nested.should be(tc_nested)
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'allows nested constants to be selectively transferred to a stub module' do
|
335
|
+
stub = Module.new
|
336
|
+
stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
|
337
|
+
stub::M.should eq(:m)
|
338
|
+
stub::N.should eq(:n)
|
339
|
+
defined?(stub::Nested).should be_false
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'raises an error if asked to transfer nested constants but given an object that does not support them' do
|
343
|
+
original_tc = TestClass
|
344
|
+
stub = Object.new
|
345
|
+
expect {
|
346
|
+
stub_const("TestClass", stub, :transfer_nested_constants => true)
|
347
|
+
}.to raise_error(ArgumentError)
|
348
|
+
|
349
|
+
TestClass.should be(original_tc)
|
350
|
+
|
351
|
+
expect {
|
352
|
+
stub_const("TestClass", stub, :transfer_nested_constants => [:M])
|
353
|
+
}.to raise_error(ArgumentError)
|
354
|
+
|
355
|
+
TestClass.should be(original_tc)
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'raises an error if asked to transfer nested constants on a constant that does not support nested constants' do
|
359
|
+
stub = Module.new
|
360
|
+
expect {
|
361
|
+
stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => true)
|
362
|
+
}.to raise_error(ArgumentError)
|
363
|
+
|
364
|
+
TOP_LEVEL_VALUE_CONST.should eq(7)
|
365
|
+
|
366
|
+
expect {
|
367
|
+
stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => [:M])
|
368
|
+
}.to raise_error(ArgumentError)
|
369
|
+
|
370
|
+
TOP_LEVEL_VALUE_CONST.should eq(7)
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'raises an error if asked to transfer a nested constant that is not defined' do
|
374
|
+
original_tc = TestClass
|
375
|
+
defined?(TestClass::V).should be_false
|
376
|
+
stub = Module.new
|
377
|
+
|
378
|
+
expect {
|
379
|
+
stub_const("TestClass", stub, :transfer_nested_constants => [:V])
|
380
|
+
}.to raise_error(/cannot transfer nested constant.*V/i)
|
381
|
+
|
382
|
+
TestClass.should be(original_tc)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
context 'for a loaded nested constant' do
|
387
|
+
it_behaves_like "loaded constant stubbing", "TestClass::Nested"
|
388
|
+
end
|
389
|
+
|
390
|
+
context 'for a loaded deeply nested constant' do
|
391
|
+
it_behaves_like "loaded constant stubbing", "TestClass::Nested::NestedEvenMore"
|
392
|
+
end
|
393
|
+
|
394
|
+
context 'for an unloaded unnested constant' do
|
395
|
+
it_behaves_like "unloaded constant stubbing", "X"
|
396
|
+
end
|
397
|
+
|
398
|
+
context 'for an unloaded nested constant' do
|
399
|
+
it_behaves_like "unloaded constant stubbing", "X::Y"
|
400
|
+
|
401
|
+
it 'removes the root constant when rspec clears its mocks' do
|
402
|
+
defined?(X).should be_false
|
403
|
+
stub_const("X::Y", 7)
|
404
|
+
reset_rspec_mocks
|
405
|
+
defined?(X).should be_false
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
context 'for an unloaded deeply nested constant' do
|
410
|
+
it_behaves_like "unloaded constant stubbing", "X::Y::Z"
|
411
|
+
|
412
|
+
it 'removes the root constant when rspec clears its mocks' do
|
413
|
+
defined?(X).should be_false
|
414
|
+
stub_const("X::Y::Z", 7)
|
415
|
+
reset_rspec_mocks
|
416
|
+
defined?(X).should be_false
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
context 'for an unloaded constant nested within a loaded constant' do
|
421
|
+
it_behaves_like "unloaded constant stubbing", "TestClass::X"
|
422
|
+
|
423
|
+
it 'removes the unloaded constant but leaves the loaded constant when rspec resets its mocks' do
|
424
|
+
defined?(TestClass).should be_true
|
425
|
+
defined?(TestClass::X).should be_false
|
426
|
+
stub_const("TestClass::X", 7)
|
427
|
+
reset_rspec_mocks
|
428
|
+
defined?(TestClass).should be_true
|
429
|
+
defined?(TestClass::X).should be_false
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
context 'for an unloaded constant nested deeply within a deeply nested loaded constant' do
|
434
|
+
it_behaves_like "unloaded constant stubbing", "TestClass::Nested::NestedEvenMore::X::Y::Z"
|
435
|
+
|
436
|
+
it 'removes the first unloaded constant but leaves the loaded nested constant when rspec resets its mocks' do
|
437
|
+
defined?(TestClass::Nested::NestedEvenMore).should be_true
|
438
|
+
defined?(TestClass::Nested::NestedEvenMore::X).should be_false
|
439
|
+
stub_const("TestClass::Nested::NestedEvenMore::X::Y::Z", 7)
|
440
|
+
reset_rspec_mocks
|
441
|
+
defined?(TestClass::Nested::NestedEvenMore).should be_true
|
442
|
+
defined?(TestClass::Nested::NestedEvenMore::X).should be_false
|
443
|
+
end
|
444
|
+
end
|
137
445
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-fire
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &2153330640 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2153330640
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2153330120 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '2.5'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2153330120
|
36
36
|
description:
|
37
37
|
email:
|
38
38
|
- hello@xaviershay.com
|