spy 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +8 -2
- data/README.md +32 -2
- data/lib/spy/agency.rb +5 -4
- data/lib/spy/constant.rb +7 -5
- data/lib/spy/exceptions.rb +33 -0
- data/lib/spy/nest.rb +24 -12
- data/lib/spy/subroutine.rb +22 -6
- data/lib/spy/version.rb +1 -1
- data/lib/spy.rb +9 -8
- data/spec/spy/any_instance_spec.rb +3 -3
- data/spy.gemspec +1 -0
- data/test/spy/test_subroutine.rb +8 -1
- data/test/test_helper.rb +2 -0
- metadata +19 -2
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
# Spy
|
1
|
+
# Spy
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/spy.png)](http://badge.fury.io/rb/spy)
|
3
|
+
[![Build Status](https://travis-ci.org/ryanong/spy.png?branch=master)](https://travis-ci.org/ryanong/spy)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/ryanong/spy/badge.png?branch=master)](https://coveralls.io/r/ryanong/spy)
|
5
|
+
|
2
6
|
|
3
7
|
[Docs](http://rdoc.info/gems/spy/frames)
|
4
8
|
|
@@ -40,6 +44,7 @@ Fail faster, code faster.
|
|
40
44
|
* #with is not supported
|
41
45
|
* you can usually just check the call logs.
|
42
46
|
* if you do need to use this. It is probably a code smell. You either need to abstract your method more or add separate tests.
|
47
|
+
* you use `mock_model` and `stub_model` (I want to impliment this soon)
|
43
48
|
|
44
49
|
## Installation
|
45
50
|
|
@@ -75,7 +80,7 @@ Spy will raise an error if you try to stub on a method that doesn't exist.
|
|
75
80
|
You can force the creation of a stub on method that didn't exist but it really isn't suggested.
|
76
81
|
|
77
82
|
```ruby
|
78
|
-
Spy.new(book, :flamethrower).hook(force:true).and_return("burnninante")
|
83
|
+
Spy::Subroutine.new(book, :flamethrower).hook(force:true).and_return("burnninante")
|
79
84
|
```
|
80
85
|
|
81
86
|
You can also stub instance methods of Classes and Modules. This is equivalent to
|
@@ -176,6 +181,19 @@ In `test_helper.rb`
|
|
176
181
|
```ruby
|
177
182
|
require "spy"
|
178
183
|
MiniTest::TestCase.add_teardown_hook { Spy.teardown }
|
184
|
+
|
185
|
+
|
186
|
+
class TestBook < MiniTest::Unit::TestCase
|
187
|
+
def test_title
|
188
|
+
book = book.new
|
189
|
+
title_spy = spy.on(book, title)
|
190
|
+
book.title
|
191
|
+
book.title
|
192
|
+
|
193
|
+
assert title_spy.has_been_called?
|
194
|
+
assert_equal 2, title_spy.calls.count
|
195
|
+
end
|
196
|
+
end
|
179
197
|
```
|
180
198
|
|
181
199
|
### Rspec
|
@@ -189,6 +207,18 @@ RSpec.configure do |c|
|
|
189
207
|
c.after { Spy.teardown }
|
190
208
|
c.mock_with :absolutely_nothing # this is completely optional.
|
191
209
|
end
|
210
|
+
|
211
|
+
describe Book do
|
212
|
+
it "title can be called" do
|
213
|
+
book = book.new
|
214
|
+
title_spy = spy.on(book, title)
|
215
|
+
book.title
|
216
|
+
book.title
|
217
|
+
|
218
|
+
expect(title_spy).to have_been_called
|
219
|
+
expect(title_spy.calls.count).to eq(2)
|
220
|
+
end
|
221
|
+
end
|
192
222
|
```
|
193
223
|
|
194
224
|
### Test::Unit
|
data/lib/spy/agency.rb
CHANGED
@@ -10,7 +10,6 @@ module Spy
|
|
10
10
|
clear!
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
13
|
# given a spy ID it will return the associated spy
|
15
14
|
# @param id [Integer] spy object id
|
16
15
|
# @return [Nil, Subroutine, Constant, Double]
|
@@ -22,11 +21,12 @@ module Spy
|
|
22
21
|
# @param spy [Subroutine, Constant, Double]
|
23
22
|
# @return [spy]
|
24
23
|
def recruit(spy)
|
24
|
+
raise AlreadyStubbedError if @spies[spy.object_id]
|
25
25
|
case spy
|
26
26
|
when Subroutine, Constant, Double
|
27
27
|
@spies[spy.object_id] = spy
|
28
28
|
else
|
29
|
-
raise "
|
29
|
+
raise ArgumentError, "#{spy}, was not a spy"
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -34,11 +34,12 @@ module Spy
|
|
34
34
|
# @param spy [Subroutine, Constant, Double]
|
35
35
|
# @return [spy]
|
36
36
|
def retire(spy)
|
37
|
+
raise NoSpyError unless @spies[spy.object_id]
|
37
38
|
case spy
|
38
39
|
when Subroutine, Constant, Double
|
39
40
|
@spies.delete(spy.object_id)
|
40
41
|
else
|
41
|
-
raise "
|
42
|
+
raise ArgumentError, "#{spy}, was not a spy"
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
@@ -50,7 +51,7 @@ module Spy
|
|
50
51
|
when Subroutine, Constant, Double
|
51
52
|
@spies.has_key?(spy.object_id)
|
52
53
|
else
|
53
|
-
raise "
|
54
|
+
raise ArgumentError, "#{spy}, was not a spy"
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
data/lib/spy/constant.rb
CHANGED
@@ -16,8 +16,8 @@ module Spy
|
|
16
16
|
# @param base_module [Module] the module this spy should be on
|
17
17
|
# @param constant_name [Symbol] the constant this spy is watching
|
18
18
|
def initialize(base_module, constant_name)
|
19
|
-
raise "#{base_module.inspect} is not a kind of Module" unless base_module.is_a? Module
|
20
|
-
raise "#{constant_name.inspect} is not a kind of Symbol" unless constant_name.is_a? Symbol
|
19
|
+
raise ArgumentError, "#{base_module.inspect} is not a kind of Module" unless base_module.is_a? Module
|
20
|
+
raise ArgumentError, "#{constant_name.inspect} is not a kind of Symbol" unless constant_name.is_a? Symbol
|
21
21
|
@base_module, @constant_name = base_module, constant_name.to_sym
|
22
22
|
@original_value = @new_value = @previously_defined = nil
|
23
23
|
end
|
@@ -97,7 +97,7 @@ module Spy
|
|
97
97
|
end
|
98
98
|
|
99
99
|
class << self
|
100
|
-
# creates a new constant spy and hooks the constant
|
100
|
+
# finds existing spy or creates a new constant spy and hooks the constant
|
101
101
|
# @return [Constant]
|
102
102
|
def on(base_module, constant_name)
|
103
103
|
new(base_module, constant_name).hook
|
@@ -107,7 +107,9 @@ module Spy
|
|
107
107
|
# from the module
|
108
108
|
# @return [Constant]
|
109
109
|
def off(base_module, constant_name)
|
110
|
-
get(base_module, constant_name)
|
110
|
+
spy = get(base_module, constant_name)
|
111
|
+
raise NoSpyError, "#{constant_name} was not spied on #{base_module}" unless spy
|
112
|
+
spy.unhook
|
111
113
|
end
|
112
114
|
|
113
115
|
# retrieves the spy for given constnat and module or returns nil
|
@@ -115,7 +117,7 @@ module Spy
|
|
115
117
|
def get(base_module, constant_name)
|
116
118
|
nest = Nest.get(base_module)
|
117
119
|
if nest
|
118
|
-
nest.
|
120
|
+
nest.get(constant_name)
|
119
121
|
end
|
120
122
|
end
|
121
123
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Spy
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class AlreadyStubbedError < Error
|
5
|
+
def message
|
6
|
+
"Spy is already stubbed."
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class AlreadyHookedError < Error
|
11
|
+
def message
|
12
|
+
"Spy is already hooked."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NotHookedError < Error
|
17
|
+
def message
|
18
|
+
"Spy was not hooked."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class NeverHookedError < Error
|
23
|
+
def message
|
24
|
+
"Spy was never hooked."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class NoSpyError < Error
|
29
|
+
def message
|
30
|
+
"Spy could not be found"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/spy/nest.rb
CHANGED
@@ -5,26 +5,26 @@ module Spy
|
|
5
5
|
# @!attribute [r] base_module
|
6
6
|
# @return [Module] The module that the Nest is managing
|
7
7
|
#
|
8
|
-
# @!attribute [r]
|
8
|
+
# @!attribute [r] constant_spies
|
9
9
|
# @return [Hash<Symbol, Constant>] The module that the Nest is managing
|
10
10
|
|
11
11
|
|
12
|
-
attr_reader :base_module
|
12
|
+
attr_reader :base_module
|
13
13
|
|
14
14
|
def initialize(base_module)
|
15
|
-
raise "#{base_module} is not a kind of Module" unless base_module.is_a?(Module)
|
15
|
+
raise ArgumentError, "#{base_module} is not a kind of Module" unless base_module.is_a?(Module)
|
16
16
|
@base_module = base_module
|
17
|
-
@
|
17
|
+
@constant_spies = {}
|
18
18
|
end
|
19
19
|
|
20
20
|
# records that the spy is hooked
|
21
21
|
# @param spy [Constant]
|
22
22
|
# @return [self]
|
23
23
|
def add(spy)
|
24
|
-
if @
|
25
|
-
raise "#{spy.constant_name} has already been stubbed"
|
24
|
+
if @constant_spies[spy.constant_name]
|
25
|
+
raise AlreadyStubbedError, "#{spy.constant_name} has already been stubbed"
|
26
26
|
else
|
27
|
-
@
|
27
|
+
@constant_spies[spy.constant_name] = spy
|
28
28
|
end
|
29
29
|
self
|
30
30
|
end
|
@@ -33,19 +33,32 @@ module Spy
|
|
33
33
|
# @param spy [Constant]
|
34
34
|
# @return [self]
|
35
35
|
def remove(spy)
|
36
|
-
if @
|
37
|
-
@
|
36
|
+
if @constant_spies[spy.constant_name] == spy
|
37
|
+
@constant_spies.delete(spy.constant_name)
|
38
38
|
else
|
39
|
-
raise "#{spy.constant_name} was
|
39
|
+
raise NoSpyError, "#{spy.constant_name} was not stubbed on #{base_module.name}"
|
40
40
|
end
|
41
41
|
self
|
42
42
|
end
|
43
43
|
|
44
|
+
# returns a spy if the constant was added
|
45
|
+
# @param constant_name [Symbol]
|
46
|
+
# @return [Constant, nil]
|
47
|
+
def get(constant_name)
|
48
|
+
@constant_spies[constant_name]
|
49
|
+
end
|
50
|
+
|
44
51
|
# checks to see if a given constant is hooked
|
45
52
|
# @param constant_name [Symbol]
|
46
53
|
# @return [Boolean]
|
47
54
|
def hooked?(constant_name)
|
48
|
-
|
55
|
+
!!get(constant_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# list all the constants that are being stubbed
|
59
|
+
# @return [Array]
|
60
|
+
def hooked_constants
|
61
|
+
@constant_spies.keys
|
49
62
|
end
|
50
63
|
|
51
64
|
class << self
|
@@ -56,7 +69,6 @@ module Spy
|
|
56
69
|
all[base_module.name]
|
57
70
|
end
|
58
71
|
|
59
|
-
|
60
72
|
# retrieves the nest for a given module or creates it
|
61
73
|
# @param base_module [Module]
|
62
74
|
# @return [Nest]
|
data/lib/spy/subroutine.rb
CHANGED
@@ -40,7 +40,7 @@ module Spy
|
|
40
40
|
# @option opts [Symbol<:public, :protected, :private>] visibility overrides visibility with whatever method is given
|
41
41
|
# @return [self]
|
42
42
|
def hook(opts = {})
|
43
|
-
raise "#{base_object} method '#{method_name}' has already been hooked" if
|
43
|
+
raise AlreadyHookedError, "#{base_object} method '#{method_name}' has already been hooked" if self.class.get(base_object, method_name, singleton_method)
|
44
44
|
|
45
45
|
@hook_opts = opts
|
46
46
|
@original_method_visibility = method_visibility_of(method_name)
|
@@ -66,7 +66,7 @@ module Spy
|
|
66
66
|
# unhooks method from object
|
67
67
|
# @return [self]
|
68
68
|
def unhook
|
69
|
-
raise "'#{method_name}' method has not been hooked" unless hooked?
|
69
|
+
raise NeverHookedError, "'#{method_name}' method has not been hooked" unless hooked?
|
70
70
|
|
71
71
|
if original_method && method_owner == original_method.owner
|
72
72
|
method_owner.send(:define_method, method_name, original_method)
|
@@ -111,7 +111,7 @@ module Spy
|
|
111
111
|
if value.is_a?(Hash) && value.has_key?(:force)
|
112
112
|
@do_not_check_plan_arity = !!value[:force]
|
113
113
|
elsif !value.nil?
|
114
|
-
raise ArgumentError
|
114
|
+
raise ArgumentError, "value and block conflict. Choose one"
|
115
115
|
end
|
116
116
|
|
117
117
|
@plan = Proc.new
|
@@ -184,7 +184,7 @@ module Spy
|
|
184
184
|
# if the method was called it will return true
|
185
185
|
# @return [Boolean]
|
186
186
|
def has_been_called?
|
187
|
-
raise
|
187
|
+
raise NeverHookedError unless @was_hooked
|
188
188
|
calls.size > 0
|
189
189
|
end
|
190
190
|
|
@@ -192,7 +192,7 @@ module Spy
|
|
192
192
|
# @param args Arguments that should have been sent to the method
|
193
193
|
# @return [Boolean]
|
194
194
|
def has_been_called_with?(*args)
|
195
|
-
raise
|
195
|
+
raise NeverHookedError unless @was_hooked
|
196
196
|
calls.any? do |call_log|
|
197
197
|
call_log.args == args
|
198
198
|
end
|
@@ -305,7 +305,23 @@ module Spy
|
|
305
305
|
end
|
306
306
|
|
307
307
|
class << self
|
308
|
-
|
308
|
+
|
309
|
+
# retrieve the method spy from an object or create a new one
|
310
|
+
# @param base_object
|
311
|
+
# @param method_name [Symbol]
|
312
|
+
# @param singleton_method [Boolean] this a singleton method or a instance method?
|
313
|
+
# @return [Array<Subroutine>]
|
314
|
+
def on(base_object, method_name, singleton_method = true)
|
315
|
+
new(base_object, method_name, singleton_method).hook
|
316
|
+
end
|
317
|
+
|
318
|
+
def off(base_object, method_name, singleton_method = true)
|
319
|
+
spy = get(base_object, method_name, singleton_method = true)
|
320
|
+
raise NoSpyError, "#{method_name} was not spied on #{base_object}" unless spy
|
321
|
+
spy.unhook
|
322
|
+
end
|
323
|
+
|
324
|
+
# retrieve the method spy from an object or return nil
|
309
325
|
# @param base_object
|
310
326
|
# @param method_name [Symbol]
|
311
327
|
# @param singleton_method [Boolean] this a singleton method or a instance method?
|
data/lib/spy/version.rb
CHANGED
data/lib/spy.rb
CHANGED
@@ -3,6 +3,7 @@ require "spy/agency"
|
|
3
3
|
require "spy/call_log"
|
4
4
|
require "spy/constant"
|
5
5
|
require "spy/double"
|
6
|
+
require "spy/exceptions"
|
6
7
|
require "spy/nest"
|
7
8
|
require "spy/subroutine"
|
8
9
|
require "spy/version"
|
@@ -32,7 +33,7 @@ module Spy
|
|
32
33
|
if spy
|
33
34
|
spy.unhook
|
34
35
|
else
|
35
|
-
raise "#{
|
36
|
+
raise NoSpyError, "#{method_name} was not hooked on #{base_object.inspect}."
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
@@ -54,7 +55,7 @@ module Spy
|
|
54
55
|
if spy
|
55
56
|
spy.unhook
|
56
57
|
else
|
57
|
-
raise "#{
|
58
|
+
raise NoSpyError, "#{method_name} was not hooked on #{base_object.inspect}."
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
@@ -79,7 +80,7 @@ module Spy
|
|
79
80
|
Constant.on(base_module, name).and_return(result)
|
80
81
|
end
|
81
82
|
else
|
82
|
-
raise ArgumentError
|
83
|
+
raise ArgumentError, "#{constant_name.class} is an invalid input, #on only accepts Symbol, and Hash"
|
83
84
|
end
|
84
85
|
end.flatten
|
85
86
|
|
@@ -98,7 +99,7 @@ module Spy
|
|
98
99
|
|
99
100
|
spies = constant_names.map do |constant_name|
|
100
101
|
unless constant_name.is_a?(Symbol)
|
101
|
-
raise ArgumentError
|
102
|
+
raise ArgumentError, "#{constant_name.class} is an invalid input, #on only accepts Symbol, and Hash"
|
102
103
|
end
|
103
104
|
Constant.off(base_module, constant_name)
|
104
105
|
end
|
@@ -148,16 +149,16 @@ module Spy
|
|
148
149
|
|
149
150
|
private
|
150
151
|
|
151
|
-
def create_and_hook_spy(base_object, method_name, singleton_method = true
|
152
|
+
def create_and_hook_spy(base_object, method_name, singleton_method = true)
|
152
153
|
case method_name
|
153
154
|
when String, Symbol
|
154
|
-
Subroutine.
|
155
|
+
Subroutine.on(base_object, method_name, singleton_method)
|
155
156
|
when Hash
|
156
157
|
method_name.map do |name, result|
|
157
|
-
|
158
|
+
Subroutine.on(base_object, name, singleton_method).and_return(result)
|
158
159
|
end
|
159
160
|
else
|
160
|
-
raise ArgumentError
|
161
|
+
raise ArgumentError, "#{method_name.class} is an invalid input, #on only accepts String, Symbol, and Hash"
|
161
162
|
end
|
162
163
|
end
|
163
164
|
end
|
@@ -444,7 +444,7 @@ module Spy
|
|
444
444
|
|
445
445
|
context 'when used in conjunction with a `dup`' do
|
446
446
|
it "doesn't cause an infinite loop" do
|
447
|
-
|
447
|
+
Spy::Subroutine.new(Object, :some_method, false).hook(force: true)
|
448
448
|
o = Object.new
|
449
449
|
o.some_method
|
450
450
|
expect { o.dup.some_method }.to_not raise_error(SystemStackError)
|
@@ -454,7 +454,7 @@ module Spy
|
|
454
454
|
klass = Class.new do
|
455
455
|
undef_method :dup
|
456
456
|
end
|
457
|
-
|
457
|
+
Spy::Subroutine.new(Object, :some_method, false).hook(force: true)
|
458
458
|
end
|
459
459
|
|
460
460
|
it "doesn't fail when dup accepts parameters" do
|
@@ -463,7 +463,7 @@ module Spy
|
|
463
463
|
end
|
464
464
|
end
|
465
465
|
|
466
|
-
|
466
|
+
Spy::Subroutine.new(Object, :some_method, false).hook(force: true)
|
467
467
|
|
468
468
|
expect { klass.new.dup('Dup dup dup') }.to_not raise_error(ArgumentError)
|
469
469
|
end
|
data/spy.gemspec
CHANGED
data/test/spy/test_subroutine.rb
CHANGED
@@ -226,8 +226,15 @@ module Spy
|
|
226
226
|
def test_spy_get_can_retrieve_a_spy
|
227
227
|
pen_write_spy = spy_on(@pen, :write).and_return(:hello)
|
228
228
|
assert_equal :hello, @pen.write(:world)
|
229
|
-
assert_equal pen_write_spy, Subroutine.get(@pen, :write)
|
230
229
|
assert Subroutine.get(@pen, :write).has_been_called?
|
230
|
+
assert_same pen_write_spy, Subroutine.get(@pen, :write)
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_spy_hook_raises_an_error_on_an_already_hooked_method
|
234
|
+
spy_on(@pen, :write)
|
235
|
+
assert_raises AlreadyHookedError do
|
236
|
+
spy_on(@pen, :write)
|
237
|
+
end
|
231
238
|
end
|
232
239
|
end
|
233
240
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-03-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -91,6 +91,22 @@ dependencies:
|
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: coveralls
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
94
110
|
description: A simple modern mocking library that uses the spy pattern and checks
|
95
111
|
method's existence and arity.
|
96
112
|
email:
|
@@ -112,6 +128,7 @@ files:
|
|
112
128
|
- lib/spy/constant.rb
|
113
129
|
- lib/spy/core_ext/marshal.rb
|
114
130
|
- lib/spy/double.rb
|
131
|
+
- lib/spy/exceptions.rb
|
115
132
|
- lib/spy/nest.rb
|
116
133
|
- lib/spy/subroutine.rb
|
117
134
|
- lib/spy/version.rb
|