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 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
@@ -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 = allocate
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
@@ -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
- def initialize(klass)
10
- self.klass = klass
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
 
@@ -1,3 +1,3 @@
1
1
  class Surrogate
2
- VERSION = "0.6.2"
2
+ VERSION = "0.6.3"
3
3
  end
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
@@ -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.2
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-30 00:00:00.000000000 Z
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: &70208657501360 !ruby/object:Gem::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: *70208657501360
24
+ version_requirements: *70232941137100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70208657500800 !ruby/object:Gem::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: *70208657500800
35
+ version_requirements: *70232941136540
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: mountain_berry_fields
38
- requirement: &70208657500220 !ruby/object:Gem::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: *70208657500220
46
+ version_requirements: *70232941135960
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: mountain_berry_fields-rspec
49
- requirement: &70208657499660 !ruby/object:Gem::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: *70208657499660
57
+ version_requirements: *70232941135400
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: mountain_berry_fields-magic_comments
60
- requirement: &70208657499120 !ruby/object:Gem::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: *70208657499120
68
+ version_requirements: *70232941134860
69
69
  description: Framework to aid in handrolling mock/spy objects.
70
70
  email:
71
71
  - josh.cheek@gmail.com