surrogate 0.6.2 → 0.6.3
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 +7 -0
- data/Readme.md +33 -0
- data/Readme.md.mountain_berry_fields +35 -0
- data/lib/surrogate/api_comparer.rb +12 -2
- data/lib/surrogate/endower.rb +34 -13
- data/lib/surrogate/hatchery.rb +7 -3
- data/lib/surrogate/version.rb +1 -1
- data/lib/surrogate.rb +2 -2
- data/spec/defining_api_methods_spec.rb +11 -0
- data/spec/other_shit_spec.rb +53 -0
- data/todo +1 -0
- metadata +12 -12
data/Changelog.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
### 0.6.3
|
2
|
+
|
3
|
+
* Allow mass initialization of instances with `MockClass.factory key: value`
|
4
|
+
* The `.factory` method can be turned off with `Surrogate.endow self, factory: false`
|
5
|
+
* The `.factory` method can be renamed with `Surrogate.endow self, factory: :custom_factory_method_name`
|
6
|
+
* Allow mass initialization of clones with `MockClass.clone key: value`
|
7
|
+
|
1
8
|
### 0.6.2
|
2
9
|
|
3
10
|
* Make substitutability matcher go either way (you should now do `RealClass.should substitute_for SurrogateClass` eventually, doing it in the other direction will not be supported)
|
data/Readme.md
CHANGED
@@ -161,6 +161,8 @@ user.id # => 12
|
|
161
161
|
```
|
162
162
|
|
163
163
|
Use **clone** to avoid altering state on the class.
|
164
|
+
You can pass it key/value pairs to mass initialize the clone
|
165
|
+
(I think it's probably best to avoid this kind of state on classes, but do support it)
|
164
166
|
```ruby
|
165
167
|
class MockUser
|
166
168
|
Surrogate.endow self do
|
@@ -168,12 +170,43 @@ class MockUser
|
|
168
170
|
end
|
169
171
|
end
|
170
172
|
|
173
|
+
# using a clone
|
171
174
|
user_class = MockUser.clone
|
172
175
|
user_class.find
|
173
176
|
user_class.was told_to :find
|
174
177
|
MockUser.was_not told_to :find
|
178
|
+
|
179
|
+
# initializing the clone
|
180
|
+
MockUser.clone(find: nil).find.should be_nil
|
175
181
|
```
|
176
182
|
|
183
|
+
**Mass initialize** with `.factory(key: value)`, this can be turned off by passing
|
184
|
+
`factory: false` as an option to Surrogate.endow, or given a different name if
|
185
|
+
the value is the new name.
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# default factory method
|
189
|
+
class MockUserWithFactory
|
190
|
+
Surrogate.endow self
|
191
|
+
define(:name) { 'Samantha' }
|
192
|
+
define(:age) { 83 }
|
193
|
+
end
|
194
|
+
|
195
|
+
user = MockUserWithFactory.factory name: 'Jim', age: 26
|
196
|
+
user.name # => "Jim"
|
197
|
+
user.age # => 26
|
198
|
+
|
199
|
+
# use a different name
|
200
|
+
class MockUserWithoutFactory
|
201
|
+
Surrogate.endow self, factory: :construct
|
202
|
+
define(:name) { 'Samantha' }
|
203
|
+
define(:age) { 83 }
|
204
|
+
end
|
205
|
+
|
206
|
+
user = MockUserWithoutFactory.construct name: 'Milla'
|
207
|
+
user.name # => "Milla"
|
208
|
+
user.age # => 83
|
209
|
+
```
|
177
210
|
|
178
211
|
|
179
212
|
RSpec Integration
|
@@ -191,6 +191,8 @@ user.id # => 12
|
|
191
191
|
```
|
192
192
|
|
193
193
|
Use **clone** to avoid altering state on the class.
|
194
|
+
You can pass it key/value pairs to mass initialize the clone
|
195
|
+
(I think it's probably best to avoid this kind of state on classes, but do support it)
|
194
196
|
```ruby
|
195
197
|
<% test 'clone to avoid mutating state', with: :rspec, context: 'generic it block' do %>
|
196
198
|
class MockUser
|
@@ -199,13 +201,46 @@ class MockUser
|
|
199
201
|
end
|
200
202
|
end
|
201
203
|
|
204
|
+
# using a clone
|
202
205
|
user_class = MockUser.clone
|
203
206
|
user_class.find
|
204
207
|
user_class.was told_to :find
|
205
208
|
MockUser.was_not told_to :find
|
209
|
+
|
210
|
+
# initializing the clone
|
211
|
+
MockUser.clone(find: nil).find.should be_nil
|
206
212
|
<% end %>
|
207
213
|
```
|
208
214
|
|
215
|
+
**Mass initialize** with `.factory(key: value)`, this can be turned off by passing
|
216
|
+
`factory: false` as an option to Surrogate.endow, or given a different name if
|
217
|
+
the value is the new name.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
<% test 'factory method', with: :magic_comments do %>
|
221
|
+
# default factory method
|
222
|
+
class MockUserWithFactory
|
223
|
+
Surrogate.endow self
|
224
|
+
define(:name) { 'Samantha' }
|
225
|
+
define(:age) { 83 }
|
226
|
+
end
|
227
|
+
|
228
|
+
user = MockUserWithFactory.factory name: 'Jim', age: 26
|
229
|
+
user.name # => "Jim"
|
230
|
+
user.age # => 26
|
231
|
+
|
232
|
+
# use a different name
|
233
|
+
class MockUserWithoutFactory
|
234
|
+
Surrogate.endow self, factory: :construct
|
235
|
+
define(:name) { 'Samantha' }
|
236
|
+
define(:age) { 83 }
|
237
|
+
end
|
238
|
+
|
239
|
+
user = MockUserWithoutFactory.construct name: 'Milla'
|
240
|
+
user.name # => "Milla"
|
241
|
+
user.age # => 83
|
242
|
+
<% end %>
|
243
|
+
```
|
209
244
|
|
210
245
|
|
211
246
|
RSpec Integration
|
@@ -62,12 +62,22 @@ class Surrogate
|
|
62
62
|
|
63
63
|
# there is a lot of duplication in these next four methods -.-
|
64
64
|
# idk if there is something we can do about it.
|
65
|
+
#
|
66
|
+
# The class methods below ignore the .clone method, b/c it's interface is intentionally different.
|
67
|
+
# This is so stupid, it needs to get smarter, it needs a way for the Surrogate or the user to tell it
|
68
|
+
# what to care about, it shouldn't have to know about what the endower is doing.
|
69
|
+
#
|
70
|
+
# In general, this code is just really fucking bad. I'd like to create some sort of method object
|
71
|
+
# that can encapsulate all of this information (mthod name, param names, types, existence)
|
72
|
+
# but I can't quite see it yet, and I'm scared to refactor to it since this code is tested primarily
|
73
|
+
# through unit tests (this is one of the few places I resorted to unit tests, and I'm so unhappy that
|
74
|
+
# I did).
|
65
75
|
|
66
76
|
# types are only shown for methods on both objects
|
67
77
|
def class_types
|
68
78
|
class_methods_that_should_match.each_with_object Hash.new do |name, hash|
|
69
79
|
surrogate_type, actual_type = class_types_for name
|
70
|
-
next if surrogate_type == actual_type
|
80
|
+
next if surrogate_type == actual_type || name == :clone # ugh :(
|
71
81
|
hash[name] = { surrogate: surrogate_type, actual: actual_type }
|
72
82
|
end
|
73
83
|
end
|
@@ -85,7 +95,7 @@ class Surrogate
|
|
85
95
|
def class_names
|
86
96
|
class_methods_that_should_match.each_with_object Hash.new do |method_name, hash|
|
87
97
|
surrogate_name, actual_name = class_parameter_names_for method_name
|
88
|
-
next if surrogate_name == actual_name
|
98
|
+
next if surrogate_name == actual_name || method_name == :clone # ugh :(
|
89
99
|
hash[method_name] = { surrogate: surrogate_name, actual: actual_name }
|
90
100
|
end
|
91
101
|
end
|
data/lib/surrogate/endower.rb
CHANGED
@@ -5,6 +5,14 @@ class Surrogate
|
|
5
5
|
# please refactor me! ...may not be possible :(
|
6
6
|
# Can we move all method definitions into this class?
|
7
7
|
class Endower
|
8
|
+
def self.uninitialized_instance_for(surrogate_class)
|
9
|
+
instance = surrogate_class.allocate
|
10
|
+
hatchery = surrogate_class.instance_variable_get :@hatchery
|
11
|
+
surrogate_class.last_instance = instance
|
12
|
+
instance.instance_variable_set :@hatchling, Hatchling.new(instance, hatchery)
|
13
|
+
instance
|
14
|
+
end
|
15
|
+
|
8
16
|
def self.add_hook(&block)
|
9
17
|
hooks << block
|
10
18
|
end
|
@@ -13,14 +21,14 @@ class Surrogate
|
|
13
21
|
@hooks ||= []
|
14
22
|
end
|
15
23
|
|
16
|
-
def self.endow(klass, &block)
|
17
|
-
new(klass, &block).endow
|
24
|
+
def self.endow(klass, options, &block)
|
25
|
+
new(klass, options, &block).endow
|
18
26
|
end
|
19
27
|
|
20
|
-
attr_accessor :klass, :block
|
28
|
+
attr_accessor :klass, :block, :options
|
21
29
|
|
22
|
-
def initialize(klass, &block)
|
23
|
-
self.klass, self.block = klass, block
|
30
|
+
def initialize(klass, options, &block)
|
31
|
+
self.klass, self.options, self.block = klass, options, block
|
24
32
|
end
|
25
33
|
|
26
34
|
def endow
|
@@ -32,8 +40,9 @@ class Surrogate
|
|
32
40
|
|
33
41
|
def endow_klass
|
34
42
|
klass.extend ClassMethods
|
35
|
-
add_hatchery_to klass
|
43
|
+
add_hatchery_to klass, options # do we want to pick out which options to pass?
|
36
44
|
enable_defining_methods klass
|
45
|
+
enable_factory klass, options.fetch(:factory, :factory)
|
37
46
|
klass.send :include, InstanceMethods
|
38
47
|
enable_generic_override klass
|
39
48
|
invoke_hooks klass
|
@@ -63,8 +72,8 @@ class Surrogate
|
|
63
72
|
klass.singleton_class
|
64
73
|
end
|
65
74
|
|
66
|
-
def add_hatchery_to(klass)
|
67
|
-
klass.instance_variable_set :@hatchery, Surrogate::Hatchery.new(klass)
|
75
|
+
def add_hatchery_to(klass, options={})
|
76
|
+
klass.instance_variable_set :@hatchery, Surrogate::Hatchery.new(klass, options)
|
68
77
|
end
|
69
78
|
|
70
79
|
def enable_defining_methods(klass)
|
@@ -72,6 +81,19 @@ class Surrogate
|
|
72
81
|
@hatchery.define method_name, options, &block
|
73
82
|
end
|
74
83
|
end
|
84
|
+
|
85
|
+
def enable_factory(klass, factory_name)
|
86
|
+
return unless factory_name
|
87
|
+
klass.define_singleton_method factory_name do |overrides={}|
|
88
|
+
instance = begin
|
89
|
+
new
|
90
|
+
rescue ArgumentError
|
91
|
+
Endower.uninitialized_instance_for self
|
92
|
+
end
|
93
|
+
overrides.each { |attribute, value| instance.will_override attribute, value }
|
94
|
+
instance
|
95
|
+
end
|
96
|
+
end
|
75
97
|
end
|
76
98
|
|
77
99
|
|
@@ -80,22 +102,21 @@ class Surrogate
|
|
80
102
|
|
81
103
|
# Should this be dup? (dup seems to copy singleton methods) and may be able to use #initialize_copy to reset ivars
|
82
104
|
# Can we just remove this feature an instead provide a reset feature which could be hooked into in before/after blocks (e.g. https://github.com/rspec/rspec-core/blob/622505d616d950ed53d12c6e82dbb953ba6241b4/lib/rspec/core/mocking/with_rspec.rb)
|
83
|
-
def clone
|
105
|
+
def clone(overrides={})
|
84
106
|
hatchling, hatchery, parent_name = @hatchling, @hatchery, name
|
85
107
|
Class.new self do
|
86
108
|
extend Module.new { define_method(:name) { parent_name && parent_name + '.clone' } } # inherit the name -- use module so that ApiComparison comes out correct (real classes inherit their name method)
|
87
|
-
Surrogate.endow self do
|
109
|
+
Surrogate.endow self, hatchery.options do
|
88
110
|
hatchling.api_methods.each { |name, options| define name, options.to_hash, &options.default_proc }
|
89
111
|
end
|
90
112
|
hatchery.api_methods.each { |name, options| define name, options.to_hash, &options.default_proc }
|
113
|
+
overrides.each { |attribute, value| @hatchling.prepare_method attribute, [value] }
|
91
114
|
end
|
92
115
|
end
|
93
116
|
|
94
117
|
# Custom new, because user can define initialize, and we need to record it
|
95
118
|
def new(*args)
|
96
|
-
instance =
|
97
|
-
self.last_instance = instance
|
98
|
-
instance.instance_variable_set :@hatchling, Hatchling.new(instance, @hatchery)
|
119
|
+
instance = Endower.uninitialized_instance_for self
|
99
120
|
instance.__send__ :initialize, *args
|
100
121
|
instance
|
101
122
|
end
|
data/lib/surrogate/hatchery.rb
CHANGED
@@ -4,10 +4,14 @@ class Surrogate
|
|
4
4
|
# The hatchlings are added to the instances, and they look here
|
5
5
|
# to find out about how their methods are implemented.
|
6
6
|
class Hatchery
|
7
|
-
attr_accessor :klass
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
# currently options don't do anything,
|
9
|
+
# they're just there because the hatchery is the definition of the surrogate
|
10
|
+
# so it makes sense for it to store values used by the endower when constructing clones
|
11
|
+
attr_accessor :klass, :options
|
12
|
+
|
13
|
+
def initialize(klass, options)
|
14
|
+
self.klass, self.options = klass, options
|
11
15
|
klass_can_define_api_methods
|
12
16
|
end
|
13
17
|
|
data/lib/surrogate/version.rb
CHANGED
data/lib/surrogate.rb
CHANGED
@@ -10,8 +10,8 @@ require 'surrogate/invocation'
|
|
10
10
|
class Surrogate
|
11
11
|
UnpreparedMethodError = Class.new StandardError
|
12
12
|
|
13
|
-
def self.endow(klass, &block)
|
14
|
-
Endower.endow klass, &block
|
13
|
+
def self.endow(klass, options={}, &block)
|
14
|
+
Endower.endow klass, options, &block
|
15
15
|
klass
|
16
16
|
end
|
17
17
|
end
|
@@ -292,6 +292,17 @@ describe 'define' do
|
|
292
292
|
superclass.clone.new.should be_a_kind_of superclass
|
293
293
|
end
|
294
294
|
|
295
|
+
it 'can be mass initialized by passing a hash of attributes and values when cloning' do
|
296
|
+
pristine_klass = Surrogate.endow(Class.new) do
|
297
|
+
define(:find) { |n| 123 }
|
298
|
+
define(:bind) { 'abc' }
|
299
|
+
end
|
300
|
+
pristine_klass.clone.find(1).should == 123
|
301
|
+
pristine_klass.clone(find: 456, bind: 'def').find(1).should == 456
|
302
|
+
pristine_klass.clone.bind.should == 'abc'
|
303
|
+
pristine_klass.clone(find: 456, bind: 'def').bind.should == 'def'
|
304
|
+
end
|
305
|
+
|
295
306
|
describe '.name' do
|
296
307
|
it 'is nil for anonymous classes' do
|
297
308
|
Surrogate.endow(Class.new).clone.name.should be_nil
|
data/spec/other_shit_spec.rb
CHANGED
@@ -183,3 +183,56 @@ describe 'inspect methods' do
|
|
183
183
|
end
|
184
184
|
end
|
185
185
|
end
|
186
|
+
|
187
|
+
describe '.factory' do
|
188
|
+
it 'allows you to initialize the values of the returned mock' do
|
189
|
+
klass = Surrogate.endow(Class.new).define(:a){'a'}.define(:b){'b'}
|
190
|
+
klass.factory(a: 'A').a.should == 'A'
|
191
|
+
klass.factory(a: 'A').b.should == 'b'
|
192
|
+
klass.new.a.should == 'a'
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'can be turned off at the class level with Surrogate.endow self factory: false' do
|
196
|
+
klass = Surrogate.endow(Class.new, factory: false).define(:a) { 'a' }
|
197
|
+
expect { klass.factory a: 'A' }.to raise_error NoMethodError
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'can be given a different method name by passing the name to the endower' do
|
201
|
+
klass = Surrogate.endow(Class.new, factory: :construct).define(:a) { 'a' }
|
202
|
+
expect { klass.factory a: 'A' }.to raise_error NoMethodError
|
203
|
+
klass.construct(a: 'A').a.should == 'A'
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'retains its setting on clones' do
|
207
|
+
clone = Surrogate.endow(Class.new, factory: :construct).define(:a){'a'}.clone
|
208
|
+
expect { clone.factory a: 'A' }.to raise_error NoMethodError
|
209
|
+
clone.construct(a: 'A').a.should == 'A'
|
210
|
+
clone.new.a.should == 'a'
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'when the initialize method can be invoked without args' do
|
214
|
+
let(:klass) { Surrogate.endow(Class.new).define(:initialize) { } }
|
215
|
+
let!(:instance) { klass.factory }
|
216
|
+
|
217
|
+
it 'is invoked' do
|
218
|
+
instance.was initialized_with no_args
|
219
|
+
end
|
220
|
+
|
221
|
+
specify 'the instance is recorded as the last instance' do
|
222
|
+
klass.last_instance.should == instance
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'when the initialize method cannot be invoked without args' do
|
227
|
+
let(:klass) { Surrogate.endow(Class.new).define(:initialize) {|arg|} }
|
228
|
+
let!(:instance) { klass.factory }
|
229
|
+
|
230
|
+
it 'is NOT invoked' do
|
231
|
+
instance.was_not told_to :initialize
|
232
|
+
end
|
233
|
+
|
234
|
+
specify 'the instance is recorded as the last instance' do
|
235
|
+
klass.last_instance.should == instance
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
data/todo
CHANGED
@@ -10,6 +10,7 @@ Urgent (things I want to do immediately, formatted as the git commits I will use
|
|
10
10
|
TODO (next up after urgent, these will happen whenever I get around to it)
|
11
11
|
--------------------------------------------------------------------------
|
12
12
|
|
13
|
+
* when trying to set a return value, error is NoMethodError will_have_whatever, but would be nice for it to say `UndefinedMethodError bad_stuff is not defined`
|
13
14
|
* Eventually it should be smart enough to not ignore natively implemented code with a [[:req]] argument
|
14
15
|
* Remove dependency on all of RSpec and only depend on rspec-core, then have AC tests for the other shit
|
15
16
|
* Add a better explanation for motivations
|
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.3
|
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-10-
|
12
|
+
date: 2012-10-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70232941137100 !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: *70232941137100
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70232941136540 !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: *70232941136540
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mountain_berry_fields
|
38
|
-
requirement: &
|
38
|
+
requirement: &70232941135960 !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: *70232941135960
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mountain_berry_fields-rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &70232941135400 !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: *70232941135400
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: mountain_berry_fields-magic_comments
|
60
|
-
requirement: &
|
60
|
+
requirement: &70232941134860 !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: *70232941134860
|
69
69
|
description: Framework to aid in handrolling mock/spy objects.
|
70
70
|
email:
|
71
71
|
- josh.cheek@gmail.com
|