surrogate 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|