surrogate 0.6.0 → 0.6.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/Changelog.md +6 -0
- data/Readme.md +5 -3
- data/Readme.md.mountain_berry_fields +5 -3
- data/lib/surrogate/endower.rb +12 -4
- data/lib/surrogate/hatchling.rb +30 -5
- data/lib/surrogate/rspec/block_asserter.rb +127 -0
- data/lib/surrogate/rspec/with_filter.rb +2 -68
- data/lib/surrogate/version.rb +1 -1
- data/spec/defining_api_methods_spec.rb +55 -39
- data/spec/rspec/block_support_spec.rb +57 -7
- data/spec/unit/map_method_name_to_ivar_spec.rb +39 -0
- data/todo +1 -9
- metadata +15 -12
data/Changelog.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
### 0.6.1
|
2
|
+
|
3
|
+
* bang methods map to ivars suffixed with `_b`, because you can't have a bang in an ivar name
|
4
|
+
* Add general syntax for overriding values (e.g. for use with operators) `will_overrides`
|
5
|
+
* block assertions can specify that exceptions should get raised (still shitty error messages, though) The interface mimicks RSpec's `#raise_error` matcher
|
6
|
+
|
1
7
|
### 0.6.0
|
2
8
|
|
3
9
|
* Setting an override still requires the invoking code to call with the correct signature
|
data/Readme.md
CHANGED
@@ -78,15 +78,16 @@ end
|
|
78
78
|
MockClient.new.request 3 # => ["result1", "result2", "result3"]
|
79
79
|
```
|
80
80
|
|
81
|
-
You don't need a **default if you set the ivar** of the same name (replace `?` with `_p` for predicates, since you can't have question marks in ivar names)
|
81
|
+
You don't need a **default if you set the ivar** of the same name (replace `?` with `_p` for predicates, and `!` with `_b` for bang methods, since you can't have question marks or bangs in ivar names)
|
82
82
|
Note that methods without bodies will not have their arguments checked, and will not be asserted against when comparing signatures.
|
83
83
|
|
84
84
|
```ruby
|
85
85
|
class MockClient
|
86
86
|
Surrogate.endow self
|
87
|
-
define(:initialize) { |id| @id, @connected_p = id, true }
|
87
|
+
define(:initialize) { |id| @id, @connected_p, @reconnect_b = id, true, true }
|
88
88
|
define :id
|
89
89
|
define :connected?
|
90
|
+
define :reconnect!
|
90
91
|
end
|
91
92
|
MockClient.new(12).id # => 12
|
92
93
|
```
|
@@ -458,6 +459,7 @@ Special Thanks
|
|
458
459
|
|
459
460
|
* [Kyle Hargraves](https://github.com/pd) for changing the name of his internal gem so that I could take Surrogate
|
460
461
|
* [David Chelimsky](http://blog.davidchelimsky.net/) for pairing with me to make Surrogate integrate better with RSpec
|
461
|
-
* [Corey Haines](http://coreyhaines.com/) for pairing on substitutability with me
|
462
462
|
* [Enova](http://www.enovafinancial.com/) for giving me time and motivation to work on this during Enova Labs.
|
463
463
|
* [8th Light](http://8thlight.com/) for giving me time to work on this during our weekly Wazas, and the general encouragement and interest
|
464
|
+
* The people who have paired with me on this: [Corey Haines](http://coreyhaines.com/) on substitutability, [Kori Roys](https://github.com/koriroys) on `#last_instance`, [Wai Lee](https://github.com/skatenerd) on the new assertion syntax
|
465
|
+
* The people who have contributed: [Michael Baker](with m://github.com/michaelbaker) on the readme.
|
@@ -98,16 +98,17 @@ MockClient.new.request 3 # => ["result1", "result2", "result3"]
|
|
98
98
|
<% end %>
|
99
99
|
```
|
100
100
|
|
101
|
-
You don't need a **default if you set the ivar** of the same name (replace `?` with `_p` for predicates, since you can't have question marks in ivar names)
|
101
|
+
You don't need a **default if you set the ivar** of the same name (replace `?` with `_p` for predicates, and `!` with `_b` for bang methods, since you can't have question marks or bangs in ivar names)
|
102
102
|
Note that methods without bodies will not have their arguments checked, and will not be asserted against when comparing signatures.
|
103
103
|
|
104
104
|
```ruby
|
105
105
|
<% test 'overriding default by setting the ivar', with: :magic_comments do %>
|
106
106
|
class MockClient
|
107
107
|
Surrogate.endow self
|
108
|
-
define(:initialize) { |id| @id, @connected_p = id, true }
|
108
|
+
define(:initialize) { |id| @id, @connected_p, @reconnect_b = id, true, true }
|
109
109
|
define :id
|
110
110
|
define :connected?
|
111
|
+
define :reconnect!
|
111
112
|
end
|
112
113
|
MockClient.new(12).id # => 12
|
113
114
|
<% end %>
|
@@ -548,6 +549,7 @@ Special Thanks
|
|
548
549
|
|
549
550
|
* [Kyle Hargraves](https://github.com/pd) for changing the name of his internal gem so that I could take Surrogate
|
550
551
|
* [David Chelimsky](http://blog.davidchelimsky.net/) for pairing with me to make Surrogate integrate better with RSpec
|
551
|
-
* [Corey Haines](http://coreyhaines.com/) for pairing on substitutability with me
|
552
552
|
* [Enova](http://www.enovafinancial.com/) for giving me time and motivation to work on this during Enova Labs.
|
553
553
|
* [8th Light](http://8thlight.com/) for giving me time to work on this during our weekly Wazas, and the general encouragement and interest
|
554
|
+
* The people who have paired with me on this: [Corey Haines](http://coreyhaines.com/) on substitutability, [Kori Roys](https://github.com/koriroys) on `#last_instance`, [Wai Lee](https://github.com/skatenerd) on the new assertion syntax
|
555
|
+
* The people who have contributed: [Michael Baker](with m://github.com/michaelbaker) on the readme.
|
data/lib/surrogate/endower.rb
CHANGED
@@ -32,10 +32,11 @@ class Surrogate
|
|
32
32
|
|
33
33
|
def endow_klass
|
34
34
|
klass.extend ClassMethods
|
35
|
-
add_hatchery_to
|
36
|
-
enable_defining_methods
|
35
|
+
add_hatchery_to klass
|
36
|
+
enable_defining_methods klass
|
37
37
|
klass.send :include, InstanceMethods
|
38
|
-
|
38
|
+
enable_generic_override klass
|
39
|
+
invoke_hooks klass
|
39
40
|
end
|
40
41
|
|
41
42
|
def endow_singleton_class
|
@@ -43,10 +44,17 @@ class Surrogate
|
|
43
44
|
enable_defining_methods singleton
|
44
45
|
singleton.module_eval &block if block
|
45
46
|
klass.instance_variable_set :@hatchling, Hatchling.new(klass, hatchery)
|
46
|
-
invoke_hooks
|
47
|
+
invoke_hooks singleton
|
47
48
|
klass
|
48
49
|
end
|
49
50
|
|
51
|
+
def enable_generic_override(klass)
|
52
|
+
klass.__send__ :define_method, :will_override do |method_name, *args, &block|
|
53
|
+
@hatchling.prepare_method method_name, args, &block
|
54
|
+
self
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
50
58
|
def invoke_hooks(klass)
|
51
59
|
self.class.hooks.each { |hook| hook.call klass }
|
52
60
|
end
|
data/lib/surrogate/hatchling.rb
CHANGED
@@ -25,6 +25,7 @@ class Surrogate
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def prepare_method(method_name, args, &block)
|
28
|
+
must_know method_name
|
28
29
|
set_ivar method_name, Value.factory(*args, &block)
|
29
30
|
end
|
30
31
|
|
@@ -75,11 +76,35 @@ class Surrogate
|
|
75
76
|
end
|
76
77
|
|
77
78
|
def ivar_for(method_name)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
79
|
+
method_name = method_name.to_s
|
80
|
+
if method_name.end_with? ?? then "@#{method_name.to_s.chop}_p"
|
81
|
+
elsif method_name.end_with? ?! then "@#{method_name.to_s.chop}_b"
|
82
|
+
elsif method_name == '[]' then '@_brackets'
|
83
|
+
elsif method_name == '**' then '@_splat_splat'
|
84
|
+
elsif method_name == '!@' then '@_ubang'
|
85
|
+
elsif method_name == '+@' then '@_uplus'
|
86
|
+
elsif method_name == '-@' then '@_uminus'
|
87
|
+
elsif method_name == ?* then '@_splat'
|
88
|
+
elsif method_name == ?/ then '@_divide'
|
89
|
+
elsif method_name == ?% then '@_percent'
|
90
|
+
elsif method_name == ?+ then '@_plus'
|
91
|
+
elsif method_name == ?- then '@_minus'
|
92
|
+
elsif method_name == '>>' then '@_shift_right'
|
93
|
+
elsif method_name == '<<' then '@_shift_left'
|
94
|
+
elsif method_name == ?& then '@_ampersand'
|
95
|
+
elsif method_name == ?^ then '@_caret'
|
96
|
+
elsif method_name == ?| then '@_bang'
|
97
|
+
elsif method_name == '<=' then '@_less_eq'
|
98
|
+
elsif method_name == ?< then '@_less'
|
99
|
+
elsif method_name == ?> then '@_greater'
|
100
|
+
elsif method_name == '>=' then '@_greater_eq'
|
101
|
+
elsif method_name == '<=>' then '@_spaceship'
|
102
|
+
elsif method_name == '==' then '@_2eq'
|
103
|
+
elsif method_name == '===' then '@_3eq'
|
104
|
+
elsif method_name == '!=' then '@_not_eq'
|
105
|
+
elsif method_name == '=~' then '@_eq_tilde'
|
106
|
+
elsif method_name == '!~' then '@_bang_tilde'
|
107
|
+
else "@#{method_name}"
|
83
108
|
end
|
84
109
|
end
|
85
110
|
|
@@ -0,0 +1,127 @@
|
|
1
|
+
class Surrogate
|
2
|
+
module RSpec
|
3
|
+
class WithFilter
|
4
|
+
class BlockAsserter
|
5
|
+
class RaiseAsserter
|
6
|
+
def initialize(arg, message)
|
7
|
+
@assertion = if arg.kind_of? String
|
8
|
+
match_message arg
|
9
|
+
elsif arg.kind_of? Regexp
|
10
|
+
match_regexp arg
|
11
|
+
elsif exception_and_message? arg, message
|
12
|
+
match_exception_and_message arg, message
|
13
|
+
else
|
14
|
+
raise ArgumentError, "raising(#{arg.inspect}, #{message.inspect}) are not valid arguments"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(exception)
|
19
|
+
@assertion.call exception
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def exception_and_message?(exception_class, message)
|
25
|
+
return false unless exception_class.kind_of? Class
|
26
|
+
return false unless exception_class.ancestors.include? Exception
|
27
|
+
return false unless message.kind_of?(NilClass) || message.kind_of?(String) || message.kind_of?(Regexp)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def match_message(message)
|
32
|
+
-> exception { message == exception.message }
|
33
|
+
end
|
34
|
+
|
35
|
+
def match_regexp(regexp)
|
36
|
+
-> exception { regexp =~ exception.message }
|
37
|
+
end
|
38
|
+
|
39
|
+
def match_exception_and_message(exception_class, message)
|
40
|
+
-> exception do
|
41
|
+
return false unless exception.kind_of? exception_class
|
42
|
+
return message == exception.message if message.kind_of? String
|
43
|
+
return message =~ exception.message if message.kind_of? Regexp
|
44
|
+
true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class BlockAsserter
|
51
|
+
def initialize(definition_block)
|
52
|
+
@call_with = Invocation.new []
|
53
|
+
definition_block.call self
|
54
|
+
end
|
55
|
+
|
56
|
+
def call_with(*args, &block)
|
57
|
+
@call_with = Invocation.new args, &block
|
58
|
+
end
|
59
|
+
|
60
|
+
def returns(value=nil, &block)
|
61
|
+
@returns = block || lambda { |returned| returned.should == value }
|
62
|
+
end
|
63
|
+
|
64
|
+
def before(&block)
|
65
|
+
@before = block
|
66
|
+
end
|
67
|
+
|
68
|
+
def after(&block)
|
69
|
+
@after = block
|
70
|
+
end
|
71
|
+
|
72
|
+
# arg can be a string (expected message)
|
73
|
+
# a regex (matches the actual message)
|
74
|
+
# an exception (type of exception expected to be raised)
|
75
|
+
def raising(arg, message=nil)
|
76
|
+
@raising = RaiseAsserter.new arg, message
|
77
|
+
end
|
78
|
+
|
79
|
+
def arity(n)
|
80
|
+
@arity = n
|
81
|
+
end
|
82
|
+
|
83
|
+
def matches?(block_to_test)
|
84
|
+
matches = before_matches? block_to_test
|
85
|
+
matches &&= return_value_matches? block_to_test
|
86
|
+
matches &&= arity_matches? block_to_test
|
87
|
+
matches &&= after_matches? block_to_test
|
88
|
+
matches
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# matches if no return specified, or returned value == specified value
|
94
|
+
# also matches the block
|
95
|
+
def return_value_matches?(block_to_test)
|
96
|
+
returned_value = block_to_test.call(*@call_with.args, &@call_with.block)
|
97
|
+
@returns.call returned_value if @returns
|
98
|
+
@raising.nil?
|
99
|
+
rescue ::RSpec::Expectations::ExpectationNotMetError
|
100
|
+
false
|
101
|
+
rescue Exception # !!!!!!WARNING!!!!!! (look here if things go wrong)
|
102
|
+
@raising && @raising.call($!)
|
103
|
+
end
|
104
|
+
|
105
|
+
# matches if the first time it is called, it raises nothing
|
106
|
+
def before_matches?(*)
|
107
|
+
@before_has_been_invoked || (@before && @before.call)
|
108
|
+
ensure
|
109
|
+
return @before_has_been_invoked = true unless $!
|
110
|
+
end
|
111
|
+
|
112
|
+
# matches if nothing is raised
|
113
|
+
def after_matches?(block_to_test)
|
114
|
+
@after && @after.call
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
118
|
+
def arity_matches?(block_to_test)
|
119
|
+
return true unless @arity
|
120
|
+
block_to_test.arity == @arity
|
121
|
+
end
|
122
|
+
|
123
|
+
attr_accessor :block_to_test
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -1,75 +1,9 @@
|
|
1
|
+
require 'surrogate/rspec/block_asserter'
|
2
|
+
|
1
3
|
class Surrogate
|
2
4
|
module RSpec
|
3
5
|
class WithFilter
|
4
6
|
|
5
|
-
class BlockAsserter
|
6
|
-
def initialize(definition_block)
|
7
|
-
@call_with = Invocation.new []
|
8
|
-
definition_block.call self
|
9
|
-
end
|
10
|
-
|
11
|
-
def call_with(*args, &block)
|
12
|
-
@call_with = Invocation.new args, &block
|
13
|
-
end
|
14
|
-
|
15
|
-
def returns(value=nil, &block)
|
16
|
-
@returns = block || lambda { |returned| returned.should == value }
|
17
|
-
end
|
18
|
-
|
19
|
-
def before(&block)
|
20
|
-
@before = block
|
21
|
-
end
|
22
|
-
|
23
|
-
def after(&block)
|
24
|
-
@after = block
|
25
|
-
end
|
26
|
-
|
27
|
-
def arity(n)
|
28
|
-
@arity = n
|
29
|
-
end
|
30
|
-
|
31
|
-
def matches?(block_to_test)
|
32
|
-
matches = before_matches? block_to_test
|
33
|
-
matches &&= return_value_matches? block_to_test
|
34
|
-
matches &&= arity_matches? block_to_test
|
35
|
-
matches &&= after_matches? block_to_test
|
36
|
-
matches
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
# matches if no return specified, or returned value == specified value
|
42
|
-
def return_value_matches?(block_to_test)
|
43
|
-
returned_value = block_to_test.call(*@call_with.args, &@call_with.block)
|
44
|
-
@returns.call returned_value if @returns
|
45
|
-
true
|
46
|
-
rescue ::RSpec::Expectations::ExpectationNotMetError
|
47
|
-
false
|
48
|
-
end
|
49
|
-
|
50
|
-
# matches if the first time it is called, it raises nothing
|
51
|
-
def before_matches?(*)
|
52
|
-
@before_has_been_invoked || (@before && @before.call)
|
53
|
-
ensure
|
54
|
-
return @before_has_been_invoked = true unless $!
|
55
|
-
end
|
56
|
-
|
57
|
-
# matches if nothing is raised
|
58
|
-
def after_matches?(block_to_test)
|
59
|
-
@after && @after.call
|
60
|
-
true
|
61
|
-
end
|
62
|
-
|
63
|
-
def arity_matches?(block_to_test)
|
64
|
-
return true unless @arity
|
65
|
-
block_to_test.arity == @arity
|
66
|
-
end
|
67
|
-
|
68
|
-
attr_accessor :block_to_test
|
69
|
-
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
7
|
class RSpecMatchAsserter
|
74
8
|
attr_accessor :actual_invocation, :expected_invocation
|
75
9
|
|
data/lib/surrogate/version.rb
CHANGED
@@ -65,24 +65,40 @@ describe 'define' do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
it 'returns the object' do
|
68
|
-
instance = mocked_class.new
|
69
68
|
instance.will_wink(:quickly).should equal instance
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
73
72
|
describe 'will_<api_method> with multiple arguments' do
|
74
73
|
it 'returns the object' do
|
75
|
-
instance = mocked_class.new
|
76
74
|
instance.will_wink(1, 2, 3).should equal instance
|
77
75
|
end
|
78
76
|
|
79
77
|
# Is there something useful the error could say?
|
80
78
|
it 'creates a queue of things to find and raises a QueueEmpty error if there are none left' do
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
instance.will_wink :quickly, [:slowly]
|
80
|
+
instance.wink.should == :quickly
|
81
|
+
instance.wink.should == [:slowly]
|
82
|
+
expect { instance.wink }.to raise_error Surrogate::Value::ValueQueue::QueueEmpty
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'will_override(method_name, ...)' do
|
87
|
+
it 'returns the object' do
|
88
|
+
instance.will_override(:wink, :quickly).should equal instance
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'is a dynamic way to override a value (useful for operators)' do
|
92
|
+
instance.will_override :wink, :quickly
|
93
|
+
instance.wink.should == :quickly
|
94
|
+
instance.will_override :wink, :quickly, [:slowly]
|
95
|
+
instance.wink.should == :quickly
|
96
|
+
instance.wink.should == [:slowly]
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'raises an error if you try to override a nonexistent method' do
|
100
|
+
expect { instance.will_override :whateva, 123 }
|
101
|
+
.to raise_error Surrogate::UnknownMethod, %(doesn't know "whateva", only knows "wink")
|
86
102
|
end
|
87
103
|
end
|
88
104
|
|
@@ -90,18 +106,18 @@ describe 'define' do
|
|
90
106
|
it 'raises the error on method invocation' do
|
91
107
|
mocked_class = Surrogate.endow(Class.new)
|
92
108
|
mocked_class.define :connect
|
93
|
-
|
109
|
+
instance = mocked_class.new
|
94
110
|
error = StandardError.new("some message")
|
95
111
|
|
96
112
|
# for single invocation
|
97
|
-
|
98
|
-
expect {
|
113
|
+
instance.will_connect error
|
114
|
+
expect { instance.connect }.to raise_error StandardError, "some message"
|
99
115
|
|
100
116
|
# for queue
|
101
|
-
|
102
|
-
|
103
|
-
expect {
|
104
|
-
|
117
|
+
instance.will_connect 1, error, 2
|
118
|
+
instance.connect.should == 1
|
119
|
+
expect { instance.connect }.to raise_error StandardError, "some message"
|
120
|
+
instance.connect.should == 2
|
105
121
|
end
|
106
122
|
end
|
107
123
|
end
|
@@ -122,24 +138,21 @@ describe 'define' do
|
|
122
138
|
end
|
123
139
|
|
124
140
|
it 'returns the object' do
|
125
|
-
instance = mocked_class.new
|
126
141
|
instance.will_have_age(123).should equal instance
|
127
142
|
end
|
128
143
|
end
|
129
144
|
|
130
145
|
describe 'wil_have_<api_method> with multiple arguments' do
|
131
146
|
it 'returns the object' do
|
132
|
-
instance = mocked_class.new
|
133
147
|
instance.will_have_age(1,2,3).should equal instance
|
134
148
|
end
|
135
149
|
|
136
150
|
# Is there something useful the error could say?
|
137
151
|
it 'creates a queue of things to find and raises a QueueEmpty error if there are none left' do
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
expect { mock.age }.to raise_error Surrogate::Value::ValueQueue::QueueEmpty
|
152
|
+
instance.will_have_age 12, 34
|
153
|
+
instance.age.should == 12
|
154
|
+
instance.age.should == 34
|
155
|
+
expect { instance.age }.to raise_error Surrogate::Value::ValueQueue::QueueEmpty
|
143
156
|
end
|
144
157
|
end
|
145
158
|
end
|
@@ -157,35 +170,41 @@ describe 'define' do
|
|
157
170
|
|
158
171
|
it 'raises an UnpreparedMethodError when it has no default block' do
|
159
172
|
mocked_class.define :meth
|
160
|
-
expect {
|
173
|
+
expect { instance.meth }.to raise_error Surrogate::UnpreparedMethodError, /meth/
|
161
174
|
end
|
162
175
|
|
163
176
|
it 'considers ivars of the same name to be its default when it has no suffix' do
|
164
177
|
mocked_class.define :meth
|
165
|
-
|
166
|
-
|
167
|
-
mocked.meth.should == 123
|
178
|
+
instance.instance_variable_set :@meth, 123
|
179
|
+
instance.meth.should == 123
|
168
180
|
end
|
169
181
|
|
170
182
|
it 'considers ivars ending in _p to be its default when it ends in a question mark' do
|
171
183
|
mocked_class.define :meth?
|
172
|
-
|
173
|
-
|
174
|
-
|
184
|
+
instance.instance_variable_set :@meth_p, 123
|
185
|
+
instance.meth?.should == 123
|
186
|
+
instance.will_have_meth? 456
|
187
|
+
instance.meth?.should == 456
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'considers ivars ending in _b to be its default when it ends in a bang' do
|
191
|
+
mocked_class.define :meth!
|
192
|
+
instance.instance_variable_set :@meth_b, 123
|
193
|
+
instance.meth!.should == 123
|
194
|
+
instance.will_have_meth! 456
|
195
|
+
instance.meth!.should == 456
|
175
196
|
end
|
176
197
|
|
177
198
|
it 'reverts to the default block if invoked and having no ivar' do
|
178
199
|
mocked_class.define(:meth) { 123 }
|
179
|
-
|
180
|
-
|
181
|
-
mocked.meth.should == 123
|
200
|
+
instance.instance_variable_get(:@meth).should be_nil
|
201
|
+
instance.meth.should == 123
|
182
202
|
end
|
183
203
|
|
184
204
|
it 'raises arity errors, even if the value is overridden' do
|
185
205
|
mocked_class.define(:meth) { }
|
186
|
-
|
187
|
-
|
188
|
-
expect { mocked.meth "extra", "args" }.to raise_error ArgumentError, /wrong number of arguments \(2 for 0\)/
|
206
|
+
instance.instance_variable_set :@meth, "abc"
|
207
|
+
expect { instance.meth "extra", "args" }.to raise_error ArgumentError, /wrong number of arguments \(2 for 0\)/
|
189
208
|
end
|
190
209
|
|
191
210
|
it 'does not raise arity errors, when there is no default block and the value is overridden' do
|
@@ -204,13 +223,11 @@ describe 'define' do
|
|
204
223
|
describe 'it takes a block whos return value will be used as the default' do
|
205
224
|
specify 'the block is instance evaled' do
|
206
225
|
mocked_class.define(:meth) { self }
|
207
|
-
instance = mocked_class.new
|
208
226
|
instance.meth.should equal instance
|
209
227
|
end
|
210
228
|
|
211
229
|
specify 'arguments passed to the method will be passed to the block' do
|
212
230
|
mocked_class.define(:meth) { |*args| args }
|
213
|
-
instance = mocked_class.new
|
214
231
|
instance.meth(1).should == [1]
|
215
232
|
instance.meth(1, 2).should == [1, 2]
|
216
233
|
end
|
@@ -231,9 +248,8 @@ describe 'define' do
|
|
231
248
|
it 'raises an error if asked about invocations for api methods it does not know' do
|
232
249
|
mocked_class.define :meth1
|
233
250
|
mocked_class.define :meth2
|
234
|
-
|
235
|
-
expect { invocations
|
236
|
-
expect { invocations mock, :meth3 }.to raise_error Surrogate::UnknownMethod, /doesn't know "meth3", only knows "meth1", "meth2"/
|
251
|
+
expect { invocations instance, :meth1 }.to_not raise_error
|
252
|
+
expect { invocations instance, :meth3 }.to raise_error Surrogate::UnknownMethod, /doesn't know "meth3", only knows "meth1", "meth2"/
|
237
253
|
end
|
238
254
|
end
|
239
255
|
|
@@ -3,6 +3,10 @@ require 'spec_helper'
|
|
3
3
|
# these all need error messages
|
4
4
|
describe 'RSpec matchers', 'have_been_told_to(...).with { |block| }' do
|
5
5
|
|
6
|
+
def fails(&block)
|
7
|
+
expect { block.call }.to raise_error RSpec::Expectations::ExpectationNotMetError
|
8
|
+
end
|
9
|
+
|
6
10
|
let(:dir) { Surrogate.endow(Class.new) { define(:chdir) { |dir_path| nil }}}
|
7
11
|
let(:dir_path) { '/some/dir/path' }
|
8
12
|
|
@@ -21,7 +25,7 @@ describe 'RSpec matchers', 'have_been_told_to(...).with { |block| }' do
|
|
21
25
|
dir.should have_been_told_to(:chdir).with(dir_path) { }
|
22
26
|
end
|
23
27
|
|
24
|
-
it "fails if the arguments don't match
|
28
|
+
it "fails if the block's arguments don't match" do
|
25
29
|
dir.chdir(dir_path) { }
|
26
30
|
dir.should_not have_been_told_to(:chdir).with(dir_path.reverse) { }
|
27
31
|
dir.should have_been_told_to(:chdir).with(dir_path) { }
|
@@ -61,6 +65,7 @@ describe 'RSpec matchers', 'have_been_told_to(...).with { |block| }' do
|
|
61
65
|
let(:file_name) { 'some_file_name.ext' }
|
62
66
|
let(:file_body) { 'some file body' }
|
63
67
|
|
68
|
+
# reword this a bit
|
64
69
|
describe 'the .before and .after hooks' do
|
65
70
|
specify "take blocks which it will evaluate before/after invoking the submitted_block" do
|
66
71
|
dir.chdir(dir_path) { file.write file_name, file_body }
|
@@ -70,7 +75,7 @@ describe 'RSpec matchers', 'have_been_told_to(...).with { |block| }' do
|
|
70
75
|
}
|
71
76
|
end
|
72
77
|
|
73
|
-
example "multiple invocations wrong number of times" do
|
78
|
+
example "example: multiple invocations wrong number of times" do
|
74
79
|
dir.chdir(dir_path) { file.write file_name, file_body }
|
75
80
|
dir.chdir(dir_path) { file.write file_name, file_body }
|
76
81
|
dir.should_not have_been_told_to(:chdir).times(1).with(dir_path) { |block|
|
@@ -79,7 +84,7 @@ describe 'RSpec matchers', 'have_been_told_to(...).with { |block| }' do
|
|
79
84
|
}
|
80
85
|
end
|
81
86
|
|
82
|
-
example "multiple invocations correct number of times" do
|
87
|
+
example "example: multiple invocations correct number of times" do
|
83
88
|
dir.chdir(dir_path) { file.write file_name, file_body }
|
84
89
|
dir.chdir(dir_path) { file.write file_name, file_body }
|
85
90
|
dir.should have_been_told_to(:chdir).times(2).with(dir_path) { |block|
|
@@ -97,9 +102,7 @@ describe 'RSpec matchers', 'have_been_told_to(...).with { |block| }' do
|
|
97
102
|
klass.new.meth { |a,b| }.should have_been_told_to(:meth).with { |b| b.arity 2 }
|
98
103
|
klass.new.meth { |a,b,c| }.should have_been_told_to(:meth).with { |b| b.arity 3 }
|
99
104
|
klass.new.meth { |*a| }.should have_been_told_to(:meth).with { |b| b.arity -1 }
|
100
|
-
|
101
|
-
klass.new.meth { |a| }.should have_been_told_to(:meth).with { |b| b.arity 123 }
|
102
|
-
}.to raise_error RSpec::Expectations::ExpectationNotMetError
|
105
|
+
fails { klass.new.meth { |a| }.should have_been_told_to(:meth).with { |b| b.arity 123 } }
|
103
106
|
end
|
104
107
|
end
|
105
108
|
|
@@ -124,6 +127,53 @@ describe 'RSpec matchers', 'have_been_told_to(...).with { |block| }' do
|
|
124
127
|
end
|
125
128
|
|
126
129
|
describe ".raising is like RSpec's raise_error interface" do
|
127
|
-
|
130
|
+
let(:klass) { Surrogate.endow(Class.new).define(:meth) { |&block| self } }
|
131
|
+
|
132
|
+
it 'fails when an exception is expected but not raised' do
|
133
|
+
fails { klass.new.meth { }.was told_to(:meth).with { |b| b.raising "not whatever" } }
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'fails when an exception is raised but not expected' do
|
137
|
+
fails { klass.new.meth { raise }.was told_to(:meth).with { |b| } }
|
138
|
+
end
|
139
|
+
|
140
|
+
# expect { raise "hello world" }.to raise_error "hello world"
|
141
|
+
it 'can take a string which must match the message' do
|
142
|
+
klass.new.meth { raise "whatever" }.was told_to(:meth).with { |b| b.raising "whatever" }
|
143
|
+
fails { klass.new.meth { raise "whatever" }.was told_to(:meth).with { |b| b.raising "not whatever" } }
|
144
|
+
end
|
145
|
+
|
146
|
+
# expect { raise "hello world" }.to raise_error /hello/
|
147
|
+
it 'can take a regex which must match the message' do
|
148
|
+
klass.new.meth { raise "whatever" }.was told_to(:meth).with { |b| b.raising /whatev/ }
|
149
|
+
fails { klass.new.meth { raise "whatever" }.was told_to(:meth).with { |b| b.raising /not a match/ } }
|
150
|
+
end
|
151
|
+
|
152
|
+
# expect { raise ArgumentError }.to raise_error ArgumentError
|
153
|
+
it 'can take an exception class which must be raised' do
|
154
|
+
klass.new.meth { raise ArgumentError, 'some message' }.was told_to(:meth).with { |b| b.raising ArgumentError }
|
155
|
+
fails { klass.new.meth { raise ArgumentError, 'some message' }.was told_to(:meth).with { |b| b.raising RuntimeError } }
|
156
|
+
end
|
157
|
+
|
158
|
+
# expect { raise ArgumentError, "whatever" }.to raise_error ArgumentError, "whatever"
|
159
|
+
it 'can take an exception class and string' do
|
160
|
+
klass.new.meth { raise ArgumentError, 'whatever' }.was told_to(:meth).with { |b| b.raising ArgumentError, 'whatever' }
|
161
|
+
fails { klass.new.meth { raise RuntimeError, 'whatever' }.was told_to(:meth).with { |b| b.raising ArgumentError, 'whatever' } }
|
162
|
+
fails { klass.new.meth { raise ArgumentError, 'whatever' }.was told_to(:meth).with { |b| b.raising ArgumentError, 'not whatever' } }
|
163
|
+
end
|
164
|
+
|
165
|
+
# expect { raise ArgumentError, 'abc' }.to raise_error ArgumentError, /abc/
|
166
|
+
it 'can take an exception class and regex' do
|
167
|
+
klass.new.meth { raise ArgumentError, "whatever" }.was told_to(:meth).with { |b| b.raising ArgumentError, /what/ }
|
168
|
+
fails { klass.new.meth { raise RuntimeError, "whatever" }.was told_to(:meth).with { |b| b.raising ArgumentError, /whatever/ } }
|
169
|
+
fails { klass.new.meth { raise ArgumentError, "whatever" }.was told_to(:meth).with { |b| b.raising ArgumentError, /not a match/ } }
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'raises an error when given anything else' do
|
173
|
+
bad_args = -> &block { expect { block.call }.to raise_error ArgumentError, /not valid arguments/ }
|
174
|
+
bad_args.call { klass.new.meth {}.was told_to(:meth).with { |b| b.raising Class } } # not an exception
|
175
|
+
bad_args.call { klass.new.meth {}.was told_to(:meth).with { |b| b.raising [] } } # not a string/regex
|
176
|
+
bad_args.call { klass.new.meth {}.was told_to(:meth).with { |b| b.raising ArgumentError, [] } } # 2nd param is not a string/regex
|
177
|
+
end
|
128
178
|
end
|
129
179
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'mapping method names to ivars' do
|
4
|
+
let(:mocked_class) { Surrogate.endow Class.new }
|
5
|
+
|
6
|
+
[ :[] , '@_brackets',
|
7
|
+
:** , '@_splat_splat',
|
8
|
+
:'!@' , '@_ubang',
|
9
|
+
:'+@' , '@_uplus',
|
10
|
+
:'-@' , '@_uminus',
|
11
|
+
:* , '@_splat',
|
12
|
+
:/ , '@_divide',
|
13
|
+
:% , '@_percent',
|
14
|
+
:+ , '@_plus',
|
15
|
+
:- , '@_minus',
|
16
|
+
:>> , '@_shift_right',
|
17
|
+
:<< , '@_shift_left',
|
18
|
+
:& , '@_ampersand',
|
19
|
+
:^ , '@_caret',
|
20
|
+
:| , '@_bang',
|
21
|
+
:<= , '@_less_eq',
|
22
|
+
:< , '@_less',
|
23
|
+
:> , '@_greater',
|
24
|
+
:>= , '@_greater_eq',
|
25
|
+
:<=> , '@_spaceship',
|
26
|
+
:== , '@_2eq',
|
27
|
+
:=== , '@_3eq',
|
28
|
+
:!= , '@_not_eq',
|
29
|
+
:=~ , '@_eq_tilde',
|
30
|
+
:!~ , '@_bang_tilde',
|
31
|
+
].each_slice 2 do |method_name, ivar|
|
32
|
+
it "maps #{method_name} to #{ivar}" do
|
33
|
+
mocked_class.define method_name
|
34
|
+
instance = mocked_class.new
|
35
|
+
instance.instance_variable_set ivar, 123
|
36
|
+
instance.send method_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/todo
CHANGED
@@ -2,16 +2,12 @@ Urgent (things I want to do immediately, formatted as the git commits I will use
|
|
2
2
|
---------------------------------------------------------------------------------
|
3
3
|
|
4
4
|
* be smart enough to handle method missing
|
5
|
-
* still check method args, even when value is overridden
|
6
5
|
* tests around the error messages of types
|
7
6
|
* Substitutability can check argument names
|
8
7
|
* substitute_for should not depend on rspec-expectations
|
9
8
|
* Error messages on blocks are actually useful
|
10
|
-
* Defined methods params must match arguments (BREAKING CHANGE)
|
11
|
-
* Update the readme (new syntax, and move the script into the changelog)
|
12
9
|
* error message
|
13
10
|
"Doesn't know initialize, only knows " <-- fix that shit
|
14
|
-
* blocks assertions can specify raising
|
15
11
|
|
16
12
|
TODO (next up after urgent, these will happen whenever I get around to it)
|
17
13
|
--------------------------------------------------------------------------
|
@@ -20,13 +16,10 @@ TODO (next up after urgent, these will happen whenever I get around to it)
|
|
20
16
|
* Remove dependency on all of RSpec and only depend on rspec-core, then have AC tests for the other shit
|
21
17
|
* Add a better explanation for motivations
|
22
18
|
* Figure out whether I'm supposed to be using clone or dup for the object -.^ (looks like there may also be an `initialize_copy` method I can take advantage of instead of crazy stupid shit I'm doing now)
|
23
|
-
* don't blow up when delegating to the Object#initialize with args (do I still want this, or do I want to force arity matching (and maybe even variable name matching)?)
|
24
19
|
* config: rspec_mocks loaded, whether unprepared blocks should raise or just return nil
|
25
20
|
* extract surrogate/rspec into its own gem
|
26
|
-
* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
|
27
21
|
* make substitutability matcher go either way
|
28
22
|
* make substitutability matcher not care whether either are surrogates
|
29
|
-
* Add support for operators
|
30
23
|
|
31
24
|
|
32
25
|
Future Features (Things that probably should eventually happen, but not anytime soon unless I get really inspired to work on this shit)
|
@@ -34,8 +27,7 @@ Future Features (Things that probably should eventually happen, but not anytime
|
|
34
27
|
|
35
28
|
* Can endow a class multiple times, results aggregate instead of override
|
36
29
|
* figure out how to talk about callbacks like #on_success
|
37
|
-
* have some sort of reinitialization that can hook into setup/teardown steps of test suite
|
38
|
-
* Support arity checking as part of substitutability
|
30
|
+
* have some sort of reinitialization that can hook into setup/teardown steps of test suite (maybe, or maybe I'm happy with what I have)
|
39
31
|
* Ability to disassociate the method name from the test (e.g. you shouldn't need to change a test just because you change a name)
|
40
32
|
* ability to declare normal methods as being part of the API
|
41
33
|
* ability to declare a define that uses the overridden method as the body, but can still act like an api method
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: surrogate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.1
|
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-10-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70147459182300 !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: *70147459182300
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70147459181540 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '2.4'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70147459181540
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mountain_berry_fields
|
38
|
-
requirement: &
|
38
|
+
requirement: &70147459180560 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.0.3
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70147459180560
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mountain_berry_fields-rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &70147459179620 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.0.3
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70147459179620
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: mountain_berry_fields-magic_comments
|
60
|
-
requirement: &
|
60
|
+
requirement: &70147459178820 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: 1.0.1
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70147459178820
|
69
69
|
description: Framework to aid in handrolling mock/spy objects.
|
70
70
|
email:
|
71
71
|
- josh.cheek@gmail.com
|
@@ -94,6 +94,7 @@ files:
|
|
94
94
|
- lib/surrogate/porc_reflector.rb
|
95
95
|
- lib/surrogate/rspec.rb
|
96
96
|
- lib/surrogate/rspec/abstract_failure_message.rb
|
97
|
+
- lib/surrogate/rspec/block_asserter.rb
|
97
98
|
- lib/surrogate/rspec/initialization_matcher.rb
|
98
99
|
- lib/surrogate/rspec/invocation_matcher.rb
|
99
100
|
- lib/surrogate/rspec/noun_matcher.rb
|
@@ -120,6 +121,7 @@ files:
|
|
120
121
|
- spec/spec_helper.rb
|
121
122
|
- spec/unit/api_comparer_spec.rb
|
122
123
|
- spec/unit/argument_errorizer_spec.rb
|
124
|
+
- spec/unit/map_method_name_to_ivar_spec.rb
|
123
125
|
- surrogate.gemspec
|
124
126
|
- todo
|
125
127
|
homepage: https://github.com/JoshCheek/surrogate
|
@@ -161,4 +163,5 @@ test_files:
|
|
161
163
|
- spec/spec_helper.rb
|
162
164
|
- spec/unit/api_comparer_spec.rb
|
163
165
|
- spec/unit/argument_errorizer_spec.rb
|
166
|
+
- spec/unit/map_method_name_to_ivar_spec.rb
|
164
167
|
has_rdoc:
|