spy 0.0.1
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/.gitignore +18 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +133 -0
- data/Rakefile +8 -0
- data/TODO.md +8 -0
- data/lib/spy.rb +259 -0
- data/lib/spy/double.rb +11 -0
- data/lib/spy/dsl.rb +7 -0
- data/lib/spy/version.rb +3 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/spy/and_call_original_spec.rb +152 -0
- data/spec/spy/and_yield_spec.rb +114 -0
- data/spec/spy/bug_report_10260_spec.rb +8 -0
- data/spec/spy/bug_report_10263_spec.rb +24 -0
- data/spec/spy/bug_report_496_spec.rb +18 -0
- data/spec/spy/bug_report_600_spec.rb +24 -0
- data/spec/spy/bug_report_7611_spec.rb +16 -0
- data/spec/spy/bug_report_8165_spec.rb +31 -0
- data/spec/spy/bug_report_830_spec.rb +21 -0
- data/spec/spy/bug_report_957_spec.rb +22 -0
- data/spec/spy/double_spec.rb +12 -0
- data/spec/spy/failing_argument_matchers_spec.rb +94 -0
- data/spec/spy/hash_excluding_matcher_spec.rb +67 -0
- data/spec/spy/hash_including_matcher_spec.rb +90 -0
- data/spec/spy/mock_spec.rb +734 -0
- data/spec/spy/multiple_return_value_spec.rb +119 -0
- data/spec/spy/mutate_const_spec.rb +481 -0
- data/spec/spy/nil_expectation_warning_spec.rb +56 -0
- data/spec/spy/null_object_mock_spec.rb +107 -0
- data/spec/spy/options_hash_spec.rb +35 -0
- data/spec/spy/partial_mock_spec.rb +196 -0
- data/spec/spy/passing_argument_matchers_spec.rb +142 -0
- data/spec/spy/precise_counts_spec.rb +68 -0
- data/spec/spy/serialization_spec.rb +110 -0
- data/spec/spy/stash_spec.rb +54 -0
- data/spec/spy/stub_implementation_spec.rb +62 -0
- data/spec/spy/stub_spec.rb +85 -0
- data/spec/spy/stubbed_message_expectations_spec.rb +47 -0
- data/spec/spy/test_double_spec.rb +57 -0
- data/spec/spy/to_ary_spec.rb +40 -0
- data/spy.gemspec +21 -0
- data/test/spy/test_double.rb +19 -0
- data/test/test_helper.rb +6 -0
- data/test/test_spy.rb +258 -0
- metadata +157 -0
data/lib/spy/double.rb
ADDED
data/lib/spy/dsl.rb
ADDED
data/lib/spy/version.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "rspec/autorun"
|
2
|
+
require "spy"
|
3
|
+
require "pry"
|
4
|
+
require "pry-nav"
|
5
|
+
|
6
|
+
RSpec::Matchers.define :include_method do |expected|
|
7
|
+
match do |actual|
|
8
|
+
actual.map { |m| m.to_s }.include?(expected.to_s)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.color_enabled = true
|
14
|
+
config.order = :random
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
17
|
+
config.filter_run_including :focus
|
18
|
+
config.filter_run_excluding :broken => true
|
19
|
+
|
20
|
+
|
21
|
+
config.expect_with :rspec do |expectations|
|
22
|
+
expectations.syntax = :expect
|
23
|
+
end
|
24
|
+
|
25
|
+
old_verbose = nil
|
26
|
+
config.before(:each, :silence_warnings) do
|
27
|
+
old_verbose = $VERBOSE
|
28
|
+
$VERBOSE = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
config.after(:each, :silence_warnings) do
|
32
|
+
$VERBOSE = old_verbose
|
33
|
+
end
|
34
|
+
|
35
|
+
config.after(:each) do
|
36
|
+
Spy.teardown
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "and_call_through" do
|
4
|
+
context "on a partial mock object" do
|
5
|
+
let(:klass) do
|
6
|
+
Class.new do
|
7
|
+
def meth_1
|
8
|
+
:original
|
9
|
+
end
|
10
|
+
|
11
|
+
def meth_2(x)
|
12
|
+
yield x, :additional_yielded_arg
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.new_instance
|
16
|
+
new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:instance) { klass.new }
|
22
|
+
|
23
|
+
it 'passes the received message through to the original method' do
|
24
|
+
spy = Spy.on(instance, :meth_1).and_call_through
|
25
|
+
expect(instance.meth_1).to eq(:original)
|
26
|
+
expect(spy).to have_been_called
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'passes args and blocks through to the original method' do
|
30
|
+
spy = Spy.on(instance, :meth_2).and_call_through
|
31
|
+
value = instance.meth_2(:submitted_arg) { |a, b| [a, b] }
|
32
|
+
expect(value).to eq([:submitted_arg, :additional_yielded_arg])
|
33
|
+
expect(spy).to have_been_called
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'works for singleton methods' do
|
37
|
+
def instance.foo; :bar; end
|
38
|
+
spy = Spy.on(instance, :foo).and_call_through
|
39
|
+
expect(instance.foo).to eq(:bar)
|
40
|
+
expect(spy).to have_been_called
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'works for methods added through an extended module' do
|
44
|
+
instance.extend Module.new { def foo; :bar; end }
|
45
|
+
spy = Spy.on(instance, :foo).and_call_through
|
46
|
+
expect(instance.foo).to eq(:bar)
|
47
|
+
expect(spy).to have_been_called
|
48
|
+
end
|
49
|
+
|
50
|
+
it "works for method added through an extended module onto a class's ancestor" do
|
51
|
+
sub_sub_klass = Class.new(Class.new(klass))
|
52
|
+
klass.extend Module.new { def foo; :bar; end }
|
53
|
+
spy = Spy.on(sub_sub_klass, :foo).and_call_through
|
54
|
+
expect(sub_sub_klass.foo).to eq(:bar)
|
55
|
+
expect(spy).to have_been_called
|
56
|
+
end
|
57
|
+
|
58
|
+
it "finds the method on the most direct ancestor even if the method " +
|
59
|
+
"is available on more distant ancestors" do
|
60
|
+
klass.extend Module.new { def foo; :klass_bar; end }
|
61
|
+
sub_klass = Class.new(klass)
|
62
|
+
sub_klass.extend Module.new { def foo; :sub_klass_bar; end }
|
63
|
+
spy = Spy.on(sub_klass, :foo).and_call_through
|
64
|
+
expect(sub_klass.foo).to eq(:sub_klass_bar)
|
65
|
+
expect(spy).to have_been_called
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'works for class methods defined on a superclass' do
|
69
|
+
subclass = Class.new(klass)
|
70
|
+
spy = Spy.on(subclass, :new_instance).and_call_through
|
71
|
+
expect(subclass.new_instance).to be_a(subclass)
|
72
|
+
expect(spy).to have_been_called
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'works for class methods defined on a grandparent class' do
|
76
|
+
sub_subclass = Class.new(Class.new(klass))
|
77
|
+
spy = Spy.on(sub_subclass, :new_instance).and_call_through
|
78
|
+
expect(sub_subclass.new_instance).to be_a(sub_subclass)
|
79
|
+
expect(spy).to have_been_called
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'works for class methods defined on the Class class' do
|
83
|
+
spy = Spy.on(klass, :new).and_call_through
|
84
|
+
expect(klass.new).to be_an_instance_of(klass)
|
85
|
+
expect(spy).to have_been_called
|
86
|
+
end
|
87
|
+
|
88
|
+
it "works for instance methods defined on the object's class's superclass" do
|
89
|
+
subclass = Class.new(klass)
|
90
|
+
inst = subclass.new
|
91
|
+
spy = Spy.on(inst, :meth_1).and_call_through
|
92
|
+
expect(inst.meth_1).to eq(:original)
|
93
|
+
expect(spy).to have_been_called
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'works for aliased methods' do
|
97
|
+
klass = Class.new do
|
98
|
+
class << self
|
99
|
+
alias alternate_new new
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
spy = Spy.on(klass, :alternate_new).and_call_through
|
104
|
+
expect(klass.alternate_new).to be_an_instance_of(klass)
|
105
|
+
expect(spy).to have_been_called
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'on an object that defines method_missing' do
|
109
|
+
before do
|
110
|
+
klass.class_eval do
|
111
|
+
def respond_to_missing?(name, _)
|
112
|
+
if name.to_s == "greet_jack"
|
113
|
+
true
|
114
|
+
else
|
115
|
+
super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def method_missing(name, *args)
|
120
|
+
if name.to_s == "greet_jack"
|
121
|
+
"Hello, jack"
|
122
|
+
else
|
123
|
+
super
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'works when the method_missing definition handles the message' do
|
130
|
+
spy = Spy.on(instance, :greet_jack).and_call_through
|
131
|
+
expect(instance.greet_jack).to eq("Hello, jack")
|
132
|
+
expect(spy).to have_been_called
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'raises an error on invocation if method_missing does not handle the message' do
|
136
|
+
Spy.new(instance, :not_a_handled_message).hook(force: true).and_call_through
|
137
|
+
|
138
|
+
# Note: it should raise a NoMethodError (and usually does), but
|
139
|
+
# due to a weird rspec-expectations issue (see #183) it sometimes
|
140
|
+
# raises a `NameError` when a `be_xxx` predicate matcher has been
|
141
|
+
# recently used. `NameError` is the superclass of `NoMethodError`
|
142
|
+
# so this example will pass regardless.
|
143
|
+
# If/when we solve the rspec-expectations issue, this can (and should)
|
144
|
+
# be changed to `NoMethodError`.
|
145
|
+
expect {
|
146
|
+
instance.not_a_handled_message
|
147
|
+
}.to raise_error(NameError, /not_a_handled_message/)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RSpec::Mocks::Mock do
|
4
|
+
|
5
|
+
let(:obj) { double }
|
6
|
+
|
7
|
+
describe "#and_yield" do
|
8
|
+
context "with eval context as block argument" do
|
9
|
+
|
10
|
+
it "evaluates the supplied block as it is read" do
|
11
|
+
evaluated = false
|
12
|
+
Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context|
|
13
|
+
evaluated = true
|
14
|
+
end
|
15
|
+
expect(evaluated).to be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "passes an eval context object to the supplied block" do
|
19
|
+
Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context|
|
20
|
+
expect(eval_context).not_to be_nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "evaluates the block passed to the stubbed method in the context of the supplied eval context" do
|
25
|
+
expected_eval_context = nil
|
26
|
+
actual_eval_context = nil
|
27
|
+
|
28
|
+
Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context|
|
29
|
+
expected_eval_context = eval_context
|
30
|
+
end
|
31
|
+
|
32
|
+
obj.method_that_accepts_a_block do
|
33
|
+
actual_eval_context = self
|
34
|
+
end
|
35
|
+
|
36
|
+
expect(actual_eval_context).to equal(expected_eval_context)
|
37
|
+
end
|
38
|
+
|
39
|
+
context "and no yielded arguments" do
|
40
|
+
|
41
|
+
it "passes when expectations set on the eval context are met" do
|
42
|
+
configured_eval_context = nil
|
43
|
+
Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context|
|
44
|
+
configured_eval_context = eval_context
|
45
|
+
configured_eval_context.should_receive(:foo)
|
46
|
+
end
|
47
|
+
|
48
|
+
obj.method_that_accepts_a_block do
|
49
|
+
foo
|
50
|
+
end
|
51
|
+
|
52
|
+
configured_eval_context.rspec_verify
|
53
|
+
end
|
54
|
+
|
55
|
+
it "fails when expectations set on the eval context are not met" do
|
56
|
+
configured_eval_context = nil
|
57
|
+
Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context|
|
58
|
+
configured_eval_context = eval_context
|
59
|
+
configured_eval_context.should_receive(:foo)
|
60
|
+
end
|
61
|
+
|
62
|
+
obj.method_that_accepts_a_block do
|
63
|
+
# foo is not called here
|
64
|
+
end
|
65
|
+
|
66
|
+
expect { configured_eval_context.rspec_verify }.to raise_error(RSpec::Mocks::MockExpectationError)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
context "and yielded arguments" do
|
72
|
+
|
73
|
+
it "passes when expectations set on the eval context and yielded arguments are met" do
|
74
|
+
configured_eval_context = nil
|
75
|
+
yielded_arg = Object.new
|
76
|
+
Spy.on(obj, :method_that_accepts_a_block).and_yield(yielded_arg) do |eval_context|
|
77
|
+
configured_eval_context = eval_context
|
78
|
+
configured_eval_context.should_receive(:foo)
|
79
|
+
yielded_arg.should_receive(:bar)
|
80
|
+
end
|
81
|
+
|
82
|
+
obj.method_that_accepts_a_block do |obj|
|
83
|
+
obj.bar
|
84
|
+
foo
|
85
|
+
end
|
86
|
+
|
87
|
+
configured_eval_context.rspec_verify
|
88
|
+
yielded_arg.rspec_verify
|
89
|
+
end
|
90
|
+
|
91
|
+
it "fails when expectations set on the eval context and yielded arguments are not met" do
|
92
|
+
configured_eval_context = nil
|
93
|
+
yielded_arg = Object.new
|
94
|
+
Spy.on(obj, :method_that_accepts_a_block).and_yield(yielded_arg) do |eval_context|
|
95
|
+
configured_eval_context = eval_context
|
96
|
+
configured_eval_context.should_receive(:foo)
|
97
|
+
yielded_arg.should_receive(:bar)
|
98
|
+
end
|
99
|
+
|
100
|
+
obj.method_that_accepts_a_block do |obj|
|
101
|
+
# obj.bar is not called here
|
102
|
+
# foo is not called here
|
103
|
+
end
|
104
|
+
|
105
|
+
expect { configured_eval_context.rspec_verify }.to raise_error(RSpec::Mocks::MockExpectationError)
|
106
|
+
expect { yielded_arg.rspec_verify }.to raise_error(RSpec::Mocks::MockExpectationError)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Double" do
|
4
|
+
let(:test_double) { double }
|
5
|
+
|
6
|
+
specify "when one example has an expectation inside the block passed to should_receive" do
|
7
|
+
Spy.stub(test_double, :msg).and_return do |arg|
|
8
|
+
expect(arg).to be_true #this call exposes the problem
|
9
|
+
end
|
10
|
+
begin
|
11
|
+
test_double.msg(false)
|
12
|
+
rescue Exception
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "then the next example should behave as expected instead of saying" do
|
17
|
+
test_double_spy = Spy.stub(test_double, :foobar)
|
18
|
+
test_double.foobar
|
19
|
+
expect(test_double_spy).to have_been_called
|
20
|
+
test_double.foobar
|
21
|
+
expect(test_double_spy.calls.count).to equal(2)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BugReport496
|
4
|
+
describe "a message expectation on a base class object" do
|
5
|
+
class BaseClass
|
6
|
+
end
|
7
|
+
|
8
|
+
class SubClass < BaseClass
|
9
|
+
end
|
10
|
+
|
11
|
+
it "is received" do
|
12
|
+
new_spy = Spy.on(BaseClass, :new)
|
13
|
+
SubClass.new
|
14
|
+
expect(new_spy.calls.count).to equal(1)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BugReport600
|
4
|
+
describe "stubbing a class method" do
|
5
|
+
class ExampleClass
|
6
|
+
def self.method_that_uses_define_method
|
7
|
+
define_method "defined_method" do |attributes|
|
8
|
+
load_address(address, attributes)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "works" do
|
14
|
+
define_method_spy = Spy.on(ExampleClass, :define_method)
|
15
|
+
ExampleClass.method_that_uses_define_method
|
16
|
+
|
17
|
+
expect(define_method_spy).to have_been_called_with("defined_method")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "restores the original method" do
|
21
|
+
ExampleClass.method_that_uses_define_method
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Bug7611
|
4
|
+
describe "A Partial Mock" do
|
5
|
+
class Foo; end
|
6
|
+
class Bar < Foo; end
|
7
|
+
|
8
|
+
it "respects subclasses" do
|
9
|
+
Spy.on(Foo, :new).and_return(Object.new)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should" do
|
13
|
+
expect(Bar.new.class).to eq Bar
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "An object where respond_to? is true and does not have method" do
|
4
|
+
# When should_receive(message) is sent to any object, the Proxy sends
|
5
|
+
# respond_to?(message) to that object to see if the method should be proxied.
|
6
|
+
#
|
7
|
+
# If respond_to? itself is proxied, then when the Proxy sends respond_to?
|
8
|
+
# to the object, the proxy is invoked and responds yes (if so set in the spec).
|
9
|
+
# When the object does NOT actually respond to `message`, an exception is thrown
|
10
|
+
# when trying to proxy it.
|
11
|
+
#
|
12
|
+
# The fix was to keep track of whether `respond_to?` had been proxied and, if
|
13
|
+
# so, call the munged copy of `respond_to?` on the object.
|
14
|
+
|
15
|
+
it "does not raise an exception for Object" do
|
16
|
+
obj = Object.new
|
17
|
+
obj.should_receive(:respond_to?).with(:foobar).and_return(true)
|
18
|
+
obj.should_receive(:foobar).and_return(:baz)
|
19
|
+
expect(obj.respond_to?(:foobar)).to be_true
|
20
|
+
expect(obj.foobar).to eq :baz
|
21
|
+
end
|
22
|
+
|
23
|
+
it "does not raise an exception for mock" do
|
24
|
+
obj = double("obj")
|
25
|
+
obj.should_receive(:respond_to?).with(:foobar).and_return(true)
|
26
|
+
obj.should_receive(:foobar).and_return(:baz)
|
27
|
+
expect(obj.respond_to?(:foobar)).to be_true
|
28
|
+
expect(obj.foobar).to eq :baz
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|