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.
Files changed (46) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +133 -0
  5. data/Rakefile +8 -0
  6. data/TODO.md +8 -0
  7. data/lib/spy.rb +259 -0
  8. data/lib/spy/double.rb +11 -0
  9. data/lib/spy/dsl.rb +7 -0
  10. data/lib/spy/version.rb +3 -0
  11. data/spec/spec_helper.rb +39 -0
  12. data/spec/spy/and_call_original_spec.rb +152 -0
  13. data/spec/spy/and_yield_spec.rb +114 -0
  14. data/spec/spy/bug_report_10260_spec.rb +8 -0
  15. data/spec/spy/bug_report_10263_spec.rb +24 -0
  16. data/spec/spy/bug_report_496_spec.rb +18 -0
  17. data/spec/spy/bug_report_600_spec.rb +24 -0
  18. data/spec/spy/bug_report_7611_spec.rb +16 -0
  19. data/spec/spy/bug_report_8165_spec.rb +31 -0
  20. data/spec/spy/bug_report_830_spec.rb +21 -0
  21. data/spec/spy/bug_report_957_spec.rb +22 -0
  22. data/spec/spy/double_spec.rb +12 -0
  23. data/spec/spy/failing_argument_matchers_spec.rb +94 -0
  24. data/spec/spy/hash_excluding_matcher_spec.rb +67 -0
  25. data/spec/spy/hash_including_matcher_spec.rb +90 -0
  26. data/spec/spy/mock_spec.rb +734 -0
  27. data/spec/spy/multiple_return_value_spec.rb +119 -0
  28. data/spec/spy/mutate_const_spec.rb +481 -0
  29. data/spec/spy/nil_expectation_warning_spec.rb +56 -0
  30. data/spec/spy/null_object_mock_spec.rb +107 -0
  31. data/spec/spy/options_hash_spec.rb +35 -0
  32. data/spec/spy/partial_mock_spec.rb +196 -0
  33. data/spec/spy/passing_argument_matchers_spec.rb +142 -0
  34. data/spec/spy/precise_counts_spec.rb +68 -0
  35. data/spec/spy/serialization_spec.rb +110 -0
  36. data/spec/spy/stash_spec.rb +54 -0
  37. data/spec/spy/stub_implementation_spec.rb +62 -0
  38. data/spec/spy/stub_spec.rb +85 -0
  39. data/spec/spy/stubbed_message_expectations_spec.rb +47 -0
  40. data/spec/spy/test_double_spec.rb +57 -0
  41. data/spec/spy/to_ary_spec.rb +40 -0
  42. data/spy.gemspec +21 -0
  43. data/test/spy/test_double.rb +19 -0
  44. data/test/test_helper.rb +6 -0
  45. data/test/test_spy.rb +258 -0
  46. metadata +157 -0
data/lib/spy/double.rb ADDED
@@ -0,0 +1,11 @@
1
+ class Spy
2
+ class Double
3
+ def initialize(name, *args)
4
+ @name = name
5
+
6
+ if args.size > 0
7
+ Spy.on(self,*args)
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/spy/dsl.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Spy
2
+ module Dsl
3
+ def spy
4
+ Spy
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class Spy
2
+ VERSION = "0.0.1"
3
+ end
@@ -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,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe "An RSpec Mock" do
4
+ it "hides internals in its inspect representation" do
5
+ m = double('cup')
6
+ expect(m.inspect).to match /#<RSpec::Mocks::Mock:0x[a-f0-9.]+ @name="cup">/
7
+ end
8
+ end
@@ -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