rspec-fire 0.3.0 → 0.4.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 +8 -0
- data/lib/rspec/fire.rb +111 -22
- data/rspec-fire.gemspec +1 -1
- data/spec/fire_double_spec.rb +172 -19
- metadata +6 -6
data/HISTORY
CHANGED
@@ -12,3 +12,11 @@
|
|
12
12
|
|
13
13
|
0.3.0 - 17 March 2012
|
14
14
|
* Added support for doubling constants.
|
15
|
+
|
16
|
+
0.4.0 - 1 April 2012
|
17
|
+
* Handle default args and splats correctly.
|
18
|
+
* rspec-mocks 2.9-master support.
|
19
|
+
* Allow `fire_double("Class", :foo => 17)` syntax.
|
20
|
+
* Allow `mock.stub(:foo => 17)` syntax.
|
21
|
+
* Don't count block params when determining max arity.
|
22
|
+
* Restore original const value when a const is stubbed more than once.
|
data/lib/rspec/fire.rb
CHANGED
@@ -4,6 +4,57 @@ require 'delegate'
|
|
4
4
|
|
5
5
|
module RSpec
|
6
6
|
module Fire
|
7
|
+
class SupportArityMatcher
|
8
|
+
def initialize(arity)
|
9
|
+
@arity = arity
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :arity, :method
|
13
|
+
|
14
|
+
def matches?(method)
|
15
|
+
@method = method
|
16
|
+
min_arity <= arity && arity <= max_arity
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_message_for_should
|
20
|
+
"Wrong number of arguments for #{method.name}. " +
|
21
|
+
"Expected #{arity_description}, got #{arity}."
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
INFINITY = 1/0.0
|
27
|
+
|
28
|
+
if method(:method).respond_to?(:parameters)
|
29
|
+
def max_arity
|
30
|
+
params = method.parameters
|
31
|
+
return INFINITY if params.any? { |(type, name)| type == :rest } # splat
|
32
|
+
params.count { |(type, name)| type != :block }
|
33
|
+
end
|
34
|
+
else
|
35
|
+
# On 1.8, Method#parameters does not exist.
|
36
|
+
# There's no way to distinguish between default and splat args, so
|
37
|
+
# there's no way to have it work correctly for both default and splat args,
|
38
|
+
# as far as I can tell.
|
39
|
+
# The best we can do is consider it INFINITY (to be tolerant of splat args).
|
40
|
+
def max_arity
|
41
|
+
method.arity < 0 ? INFINITY : method.arity
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def min_arity
|
46
|
+
return method.arity if method.arity >= 0
|
47
|
+
# ~ inverts the one's complement and gives us the number of required args
|
48
|
+
~method.arity
|
49
|
+
end
|
50
|
+
|
51
|
+
def arity_description
|
52
|
+
return min_arity if min_arity == max_arity
|
53
|
+
return "#{min_arity} or more" if max_arity == INFINITY
|
54
|
+
"#{min_arity} to #{max_arity}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
7
58
|
module RecursiveConstMethods
|
8
59
|
def recursive_const_get name
|
9
60
|
name.split('::').inject(Object) {|klass,name| klass.const_get name }
|
@@ -19,7 +70,6 @@ module RSpec
|
|
19
70
|
end
|
20
71
|
|
21
72
|
class ShouldProxy < SimpleDelegator
|
22
|
-
extend RSpec::Matchers::DSL
|
23
73
|
include RecursiveConstMethods
|
24
74
|
|
25
75
|
AM = RSpec::Mocks::ArgumentMatchers
|
@@ -52,19 +102,12 @@ module RSpec
|
|
52
102
|
|
53
103
|
def ensure_arity(actual)
|
54
104
|
@double.with_doubled_class do |klass|
|
55
|
-
klass.send(@method_finder, @sym).should
|
105
|
+
klass.send(@method_finder, @sym).should support_arity(actual)
|
56
106
|
end
|
57
107
|
end
|
58
108
|
|
59
|
-
|
60
|
-
|
61
|
-
method.arity >= 0 && method.arity == actual
|
62
|
-
end
|
63
|
-
|
64
|
-
failure_message_for_should do |method|
|
65
|
-
"Wrong number of arguments for #{method.name}. " +
|
66
|
-
"Expected #{method.arity}, got #{actual}."
|
67
|
-
end
|
109
|
+
def support_arity(arity)
|
110
|
+
SupportArityMatcher.new(arity)
|
68
111
|
end
|
69
112
|
end
|
70
113
|
|
@@ -83,12 +126,18 @@ module RSpec
|
|
83
126
|
end
|
84
127
|
|
85
128
|
def stub(method_name)
|
86
|
-
ensure_implemented(method_name)
|
129
|
+
ensure_implemented(method_name) unless method_name.is_a?(Hash)
|
87
130
|
super
|
88
131
|
end
|
89
132
|
|
133
|
+
def stub!(method_name)
|
134
|
+
stub(method_name)
|
135
|
+
end
|
136
|
+
|
90
137
|
def with_doubled_class
|
91
|
-
if
|
138
|
+
if original_stubbed_const_value = ConstantStubber.original_value_for(@__doubled_class_name)
|
139
|
+
yield original_stubbed_const_value
|
140
|
+
elsif recursive_const_defined?(@__doubled_class_name)
|
92
141
|
yield recursive_const_get(@__doubled_class_name)
|
93
142
|
end
|
94
143
|
end
|
@@ -135,9 +184,11 @@ module RSpec
|
|
135
184
|
|
136
185
|
# __declared_as copied from rspec/mocks definition of `double`
|
137
186
|
args.last[:__declared_as] = 'FireDouble'
|
138
|
-
|
187
|
+
|
139
188
|
@__checked_methods = :public_instance_methods
|
140
189
|
@__method_finder = :instance_method
|
190
|
+
|
191
|
+
super
|
141
192
|
end
|
142
193
|
end
|
143
194
|
|
@@ -150,8 +201,20 @@ module RSpec
|
|
150
201
|
@__checked_methods = :public_methods
|
151
202
|
@__method_finder = :method
|
152
203
|
|
153
|
-
|
154
|
-
|
204
|
+
# TestDouble was added after rspec 2.9.0, and allows proper mocking
|
205
|
+
# of public methods that have clashing private methods. See spec for
|
206
|
+
# details.
|
207
|
+
if defined?(::RSpec::Mocks::TestDouble)
|
208
|
+
::RSpec::Mocks::TestDouble.extend_onto self,
|
209
|
+
doubled_class, stubs.merge(:__declared_as => "FireClassDouble")
|
210
|
+
else
|
211
|
+
stubs.each do |message, response|
|
212
|
+
stub(message).and_return(response)
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.method_missing(name, *args)
|
216
|
+
__mock_proxy.raise_unexpected_message_error(name, *args)
|
217
|
+
end
|
155
218
|
end
|
156
219
|
|
157
220
|
def self.as_replaced_constant(options = {})
|
@@ -171,10 +234,6 @@ module RSpec
|
|
171
234
|
def self.name
|
172
235
|
@__doubled_class_name
|
173
236
|
end
|
174
|
-
|
175
|
-
def self.method_missing(name, *args)
|
176
|
-
__mock_proxy.raise_unexpected_message_error(name, *args)
|
177
|
-
end
|
178
237
|
end
|
179
238
|
end
|
180
239
|
|
@@ -190,7 +249,7 @@ module RSpec
|
|
190
249
|
|
191
250
|
class DefinedConstantReplacer
|
192
251
|
include RecursiveConstMethods
|
193
|
-
attr_reader :original_value
|
252
|
+
attr_reader :original_value, :full_constant_name
|
194
253
|
|
195
254
|
def initialize(full_constant_name, stubbed_value, transfer_nested_constants)
|
196
255
|
@full_constant_name = full_constant_name
|
@@ -259,6 +318,8 @@ module RSpec
|
|
259
318
|
class UndefinedConstantSetter
|
260
319
|
include RecursiveConstMethods
|
261
320
|
|
321
|
+
attr_reader :full_constant_name
|
322
|
+
|
262
323
|
def initialize(full_constant_name, stubbed_value)
|
263
324
|
@full_constant_name = full_constant_name
|
264
325
|
@stubbed_value = stubbed_value
|
@@ -301,10 +362,38 @@ module RSpec
|
|
301
362
|
UndefinedConstantSetter.new(constant_name, value)
|
302
363
|
end
|
303
364
|
|
365
|
+
stubbers << stubber
|
366
|
+
|
304
367
|
stubber.stub!
|
305
|
-
|
368
|
+
ensure_registered_with_rspec_mocks
|
306
369
|
stubber.original_value
|
307
370
|
end
|
371
|
+
|
372
|
+
def self.ensure_registered_with_rspec_mocks
|
373
|
+
return if @registered_with_rspec_mocks
|
374
|
+
::RSpec::Mocks.space.add(self)
|
375
|
+
@registered_with_rspec_mocks = true
|
376
|
+
end
|
377
|
+
|
378
|
+
def self.rspec_reset
|
379
|
+
@registered_with_rspec_mocks = false
|
380
|
+
|
381
|
+
# We use reverse order so that if the same constant
|
382
|
+
# was stubbed multiple times, the original value gets
|
383
|
+
# properly restored.
|
384
|
+
stubbers.reverse.each { |s| s.rspec_reset }
|
385
|
+
|
386
|
+
stubbers.clear
|
387
|
+
end
|
388
|
+
|
389
|
+
def self.stubbers
|
390
|
+
@stubbers ||= []
|
391
|
+
end
|
392
|
+
|
393
|
+
def self.original_value_for(constant_name)
|
394
|
+
stubber = stubbers.find { |s| s.full_constant_name == constant_name }
|
395
|
+
stubber.original_value if stubber
|
396
|
+
end
|
308
397
|
end
|
309
398
|
|
310
399
|
def stub_const(name, value, options = {})
|
data/rspec-fire.gemspec
CHANGED
data/spec/fire_double_spec.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
3
|
+
def use; end
|
4
|
+
private :use
|
4
5
|
|
5
|
-
|
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
|
6
|
+
TOP_LEVEL_VALUE_CONST = 7
|
12
7
|
|
13
8
|
module TestMethods
|
14
9
|
def defined_method
|
@@ -25,6 +20,7 @@ class TestObject
|
|
25
20
|
end
|
26
21
|
|
27
22
|
class TestClass
|
23
|
+
include TestMethods
|
28
24
|
extend TestMethods
|
29
25
|
|
30
26
|
M = :m
|
@@ -34,6 +30,10 @@ class TestClass
|
|
34
30
|
class NestedEvenMore
|
35
31
|
end
|
36
32
|
end
|
33
|
+
|
34
|
+
def self.use
|
35
|
+
raise "Y U NO MOCK?"
|
36
|
+
end
|
37
37
|
end
|
38
38
|
|
39
39
|
shared_examples_for 'a fire-enhanced double method' do
|
@@ -49,20 +49,26 @@ shared_examples_for 'a fire-enhanced double method' do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
shared_examples_for 'a fire-enhanced double' do
|
52
|
-
def self.should_allow(
|
53
|
-
|
52
|
+
def self.should_allow(method_parameters)
|
53
|
+
method_to_stub = method_parameters.is_a?(Hash) ?
|
54
|
+
method_parameters.keys.first : method_parameters
|
55
|
+
|
56
|
+
it "should allow #{method_to_stub}" do
|
54
57
|
lambda {
|
55
|
-
doubled_object.send(method_under_test,
|
58
|
+
doubled_object.send(method_under_test, method_parameters)
|
56
59
|
}.should_not raise_error
|
57
60
|
doubled_object.rspec_reset
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
61
|
-
def self.should_not_allow(
|
62
|
-
|
64
|
+
def self.should_not_allow(method_parameters)
|
65
|
+
method_to_stub = method_parameters.is_a?(Hash) ?
|
66
|
+
method_parameters.keys.first : method_parameters
|
67
|
+
|
68
|
+
it "should not allow #{method_to_stub}" do
|
63
69
|
lambda {
|
64
|
-
doubled_object.send(method_under_test,
|
65
|
-
}.should fail_matching("does not implement",
|
70
|
+
doubled_object.send(method_under_test, method_parameters)
|
71
|
+
}.should fail_matching("does not implement", method_to_stub)
|
66
72
|
end
|
67
73
|
end
|
68
74
|
|
@@ -136,9 +142,23 @@ shared_examples_for 'a fire-enhanced double' do
|
|
136
142
|
it_should_behave_like 'a fire-enhanced double method'
|
137
143
|
end
|
138
144
|
|
139
|
-
|
140
|
-
|
141
|
-
|
145
|
+
[ :stub, :stub! ].each do |stubber|
|
146
|
+
describe "##{stubber}" do
|
147
|
+
let(:method_under_test) { stubber }
|
148
|
+
it_should_behave_like 'a fire-enhanced double method'
|
149
|
+
|
150
|
+
context "RSpec's hash shortcut syntax" do
|
151
|
+
context 'doubled class is not loaded' do
|
152
|
+
let(:doubled_object) { fire_double("UnloadedObject") }
|
153
|
+
should_allow(:undefined_method => 123)
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'doubled class is loaded' do
|
157
|
+
should_allow(:defined_method => 456)
|
158
|
+
should_not_allow(:undefined_method => 789)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
142
162
|
end
|
143
163
|
end
|
144
164
|
|
@@ -146,6 +166,11 @@ describe '#fire_double' do
|
|
146
166
|
let(:doubled_object) { fire_double("TestObject") }
|
147
167
|
|
148
168
|
it_should_behave_like 'a fire-enhanced double'
|
169
|
+
|
170
|
+
it 'allows stubs to be passed as a hash' do
|
171
|
+
double = fire_double("TestObject", :defined_method => 17)
|
172
|
+
double.defined_method.should eq(17)
|
173
|
+
end
|
149
174
|
end
|
150
175
|
|
151
176
|
describe '#fire_class_double' do
|
@@ -178,6 +203,16 @@ describe '#fire_class_double' do
|
|
178
203
|
double.a.should eq(5)
|
179
204
|
double.b.should eq(8)
|
180
205
|
end
|
206
|
+
|
207
|
+
it 'allows private methods to be stubbed, just like on a normal test double (but unlike a partial mock)' do
|
208
|
+
mod = Module.new
|
209
|
+
mod.stub(:use)
|
210
|
+
expect { mod.use }.to raise_error(/private method `use' called/)
|
211
|
+
|
212
|
+
fire_double = fire_class_double("TestClass")
|
213
|
+
fire_double.stub(:use)
|
214
|
+
fire_double.use # should not raise an error
|
215
|
+
end if defined?(::RSpec::Mocks::TestDouble)
|
181
216
|
end
|
182
217
|
|
183
218
|
def reset_rspec_mocks
|
@@ -202,6 +237,31 @@ describe '#fire_replaced_class_double (for an existing class)' do
|
|
202
237
|
TestClass::M.should eq(:m)
|
203
238
|
TestClass::N.should eq(:n)
|
204
239
|
end
|
240
|
+
|
241
|
+
def use_doubles(class_double, instance_double)
|
242
|
+
instance_double.should_receive(:defined_method).and_return(3)
|
243
|
+
class_double.should_receive(:defined_method).and_return(4)
|
244
|
+
|
245
|
+
instance_double.defined_method.should eq(3)
|
246
|
+
class_double.defined_method.should eq(4)
|
247
|
+
|
248
|
+
expect { instance_double.should_receive(:undefined_method) }.to fail_matching("does not implement")
|
249
|
+
expect { class_double.should_receive(:undefined_method) }.to fail_matching("does not implement")
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'can be used after a declared fire_double for the same class' do
|
253
|
+
instance_double = fire_double("TestClass")
|
254
|
+
class_double = fire_replaced_class_double("TestClass")
|
255
|
+
|
256
|
+
use_doubles class_double, instance_double
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'can be used before a declared fire_double for the same class' do
|
260
|
+
class_double = fire_replaced_class_double("TestClass")
|
261
|
+
instance_double = fire_double("TestClass")
|
262
|
+
|
263
|
+
use_doubles class_double, instance_double
|
264
|
+
end
|
205
265
|
end
|
206
266
|
|
207
267
|
describe '#fire_replaced_class_double (for a non-existant class)' do
|
@@ -297,7 +357,7 @@ shared_examples_for "unloaded constant stubbing" do |const_name|
|
|
297
357
|
it 'does not remove the constant when the example manually sets it' do
|
298
358
|
begin
|
299
359
|
stub_const(const_name, 7)
|
300
|
-
stubber = RSpec::
|
360
|
+
stubber = RSpec::Fire::ConstantStubber.stubbers.first
|
301
361
|
change_const_value_to(new_const_value = Object.new)
|
302
362
|
reset_rspec_mocks
|
303
363
|
const.should equal(new_const_value)
|
@@ -322,6 +382,16 @@ describe "#stub_const" do
|
|
322
382
|
context 'for a loaded unnested constant' do
|
323
383
|
it_behaves_like "loaded constant stubbing", "TestClass"
|
324
384
|
|
385
|
+
it 'can be stubbed multiple times but still restores the original value properly' do
|
386
|
+
orig_value = TestClass
|
387
|
+
stub1, stub2 = Module.new, Module.new
|
388
|
+
stub_const("TestClass", stub1)
|
389
|
+
stub_const("TestClass", stub2)
|
390
|
+
|
391
|
+
reset_rspec_mocks
|
392
|
+
TestClass.should be(orig_value)
|
393
|
+
end
|
394
|
+
|
325
395
|
it 'allows nested constants to be transferred to a stub module' do
|
326
396
|
tc_nested = TestClass::Nested
|
327
397
|
stub = Module.new
|
@@ -443,3 +513,86 @@ describe "#stub_const" do
|
|
443
513
|
end
|
444
514
|
end
|
445
515
|
end
|
516
|
+
|
517
|
+
describe RSpec::Fire::SupportArityMatcher do
|
518
|
+
def support_arity(arity)
|
519
|
+
RSpec::Fire::SupportArityMatcher.new(arity)
|
520
|
+
end
|
521
|
+
|
522
|
+
context "a method with an exact arity" do
|
523
|
+
def two_args(a, b); end
|
524
|
+
def no_args; end
|
525
|
+
|
526
|
+
it 'passes when given the correct arity' do
|
527
|
+
method(:two_args).should support_arity(2)
|
528
|
+
method(:no_args).should support_arity(0)
|
529
|
+
end
|
530
|
+
|
531
|
+
it 'fails when given the wrong arity' do
|
532
|
+
expect {
|
533
|
+
method(:no_args).should support_arity(1)
|
534
|
+
}.to raise_error(/Expected 0, got 1/)
|
535
|
+
|
536
|
+
expect {
|
537
|
+
method(:two_args).should support_arity(1)
|
538
|
+
}.to raise_error(/Expected 2, got 1/)
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
context "a method with one required arg and two default args" do
|
543
|
+
def m(a, b=5, c=2); end
|
544
|
+
|
545
|
+
it 'passes when given 1 to 3 args' do
|
546
|
+
method(:m).should support_arity(1)
|
547
|
+
method(:m).should support_arity(2)
|
548
|
+
method(:m).should support_arity(3)
|
549
|
+
end
|
550
|
+
|
551
|
+
let(:can_distinguish_splat_from_defaults?) { method(:method).respond_to?(:parameters) }
|
552
|
+
|
553
|
+
it 'fails when given 0' do
|
554
|
+
pending("1.8 cannot distinguish default args from splats", :unless => can_distinguish_splat_from_defaults?) do
|
555
|
+
expect {
|
556
|
+
method(:m).should support_arity(0)
|
557
|
+
}.to raise_error(/Expected 1 to 3, got 0/)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
it 'fails when given more than 3' do
|
562
|
+
pending("1.8 cannot distinguish default args from splats", :unless => can_distinguish_splat_from_defaults?) do
|
563
|
+
expect {
|
564
|
+
method(:m).should support_arity(4)
|
565
|
+
}.to raise_error(/Expected 1 to 3, got 4/)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
context "a method with one required arg and a splat" do
|
571
|
+
def m(a, *b); end
|
572
|
+
|
573
|
+
it 'passes when given 1 or more' do
|
574
|
+
method(:m).should support_arity(1)
|
575
|
+
method(:m).should support_arity(20)
|
576
|
+
end
|
577
|
+
|
578
|
+
it 'fails when given 0' do
|
579
|
+
expect {
|
580
|
+
method(:m).should support_arity(0)
|
581
|
+
}.to raise_error(/Expected 1 or more, got 0/)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
context "a method with an explicit block arg" do
|
586
|
+
def m(a, &b); end
|
587
|
+
|
588
|
+
it 'passes when given 1' do
|
589
|
+
method(:m).should support_arity(1)
|
590
|
+
end
|
591
|
+
|
592
|
+
it 'fails when given 2' do
|
593
|
+
expect {
|
594
|
+
method(:m).should support_arity(2)
|
595
|
+
}.to raise_error(/Expected 1, got 2/)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
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.4.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-04-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &2160595040 !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: *2160595040
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2160594520 !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: *2160594520
|
36
36
|
description:
|
37
37
|
email:
|
38
38
|
- hello@xaviershay.com
|