surrogate 0.5.5 → 0.6.0

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.
@@ -0,0 +1,43 @@
1
+ require 'set'
2
+ class Surrogate
3
+ # reflects on the Plain Old Ruby Class to give info about methods that are useful for the comparer
4
+ class PorcReflector < Struct.new(:actual)
5
+ def methods
6
+ { instance: {
7
+ inherited: instance_inherited_methods,
8
+ other: instance_other_methods,
9
+ without_bodies: instance_without_bodies,
10
+ },
11
+ class: {
12
+ inherited: class_inherited_methods,
13
+ other: class_other_methods,
14
+ without_bodies: class_without_bodies,
15
+ },
16
+ }
17
+ end
18
+
19
+ def instance_inherited_methods
20
+ Set.new actual.instance_methods - actual.instance_methods(false)
21
+ end
22
+
23
+ def instance_other_methods
24
+ Set.new(actual.instance_methods) - instance_inherited_methods
25
+ end
26
+
27
+ def class_inherited_methods
28
+ Set.new actual.singleton_class.instance_methods - actual.singleton_class.instance_methods(false)
29
+ end
30
+
31
+ def class_other_methods
32
+ Set.new(actual.singleton_class.instance_methods) - class_inherited_methods
33
+ end
34
+
35
+ def class_without_bodies
36
+ Set.new actual.methods.select { |name| actual.method(name).parameters.any? { |param| param.size == 1 } }
37
+ end
38
+
39
+ def instance_without_bodies
40
+ Set.new actual.instance_methods.select { |name| actual.instance_method(name).parameters.any? { |param| param.size == 1 } }
41
+ end
42
+ end
43
+ end
@@ -1,6 +1,7 @@
1
1
  require 'surrogate/rspec/abstract_failure_message'
2
2
  require 'surrogate/rspec/times_predicate'
3
3
  require 'surrogate/rspec/with_filter'
4
+ require 'surrogate/surrogate_instance_reflector'
4
5
 
5
6
  class Surrogate
6
7
  module RSpec
@@ -23,7 +24,7 @@ class Surrogate
23
24
  end
24
25
 
25
26
  def invocations
26
- surrogate.invocations(method_name)
27
+ SurrogateInstanceReflector.new(surrogate).invocations(method_name)
27
28
  end
28
29
 
29
30
  def failure_message_for_should
@@ -1,16 +1,24 @@
1
1
  class Surrogate
2
2
  ::RSpec::Matchers.define :substitute_for do |original_class, options={}|
3
3
 
4
- comparison = nil
4
+ comparison = nil
5
5
  subset_only = options[:subset]
6
+ types = options.fetch :types, true
7
+
8
+ def comparing_fields(comparison, subset_only, types)
9
+ fields = {}
10
+ fields[:instance_not_on_actual ] = comparison[:instance][:not_on_actual]
11
+ fields[:class_not_on_actual ] = comparison[:class ][:not_on_actual]
12
+ fields[:instance_not_on_surrogate] = comparison[:instance][:not_on_surrogate] unless subset_only
13
+ fields[:class_not_on_surrogate ] = comparison[:class ][:not_on_surrogate] unless subset_only
14
+ fields[:instance_types ] = comparison[:instance][:types] if types
15
+ fields[:class_types ] = comparison[:class ][:types] if types
16
+ fields
17
+ end
6
18
 
7
19
  match do |mocked_class|
8
20
  comparison = ApiComparer.new(mocked_class, original_class).compare
9
- if subset_only
10
- (comparison[:instance][:not_on_actual] + comparison[:class][:not_on_actual]).empty?
11
- else
12
- (comparison[:instance].values + comparison[:class].values).inject(:+).empty?
13
- end
21
+ comparing_fields(comparison, subset_only, types).values.inject(:+).empty?
14
22
  end
15
23
 
16
24
  failure_message_for_should do
@@ -18,13 +26,21 @@ class Surrogate
18
26
  extra_class_methods = comparison[:class ][:not_on_actual ].to_a
19
27
  missing_instance_methods = comparison[:instance][:not_on_surrogate].to_a
20
28
  missing_class_methods = comparison[:class ][:not_on_surrogate].to_a
29
+ instance_type_mismatch = comparison[:instance][:types ]
30
+ class_type_mismatch = comparison[:class ][:types ]
31
+
21
32
 
22
33
  differences = []
23
34
  differences << "has extra instance methods: #{extra_instance_methods.inspect}" if extra_instance_methods.any?
24
35
  differences << "has extra class methods: #{extra_class_methods.inspect}" if extra_class_methods.any?
25
36
  differences << "is missing instance methods: #{missing_instance_methods}" if !subset_only && missing_instance_methods.any?
26
37
  differences << "is missing class methods: #{missing_class_methods}" if !subset_only && missing_class_methods.any?
27
- "Was not substitutable because surrogate " << differences.join(', ')
38
+
39
+ if types # this conditional is not tested, nor are these error messages
40
+ instance_type_mismatch.each { |name, types| differences << "##{name} had types #{types.inspect}" }
41
+ class_type_mismatch.each { |name, types| differences << ".#{name} had types #{types.inspect}" }
42
+ end
43
+ "Was not substitutable because surrogate " << differences.join("\n")
28
44
  end
29
45
 
30
46
  failure_message_for_should_not do
@@ -94,7 +94,6 @@ class Surrogate
94
94
 
95
95
 
96
96
 
97
- # rename args to invocation
98
97
  attr_accessor :expected_invocation, :block, :pass, :filter_name
99
98
 
100
99
  def initialize(args=[], filter_name=:default_filter, &block)
@@ -0,0 +1,65 @@
1
+ require 'set'
2
+ class Surrogate
3
+
4
+ # Reflects on the surrogate class to give info about methods that are useful for the comparer
5
+ #
6
+ # It might make sense to not treat instance and class differently, but instead let whoever wants to use this
7
+ # instantiate it with both the class and the singleton class.
8
+ class SurrogateClassReflector < Struct.new(:surrogate_class)
9
+ def methods
10
+ { instance: {
11
+ api: instance_api_methods,
12
+ inherited: instance_inherited_methods,
13
+ other: instance_other_methods,
14
+ without_bodies: instance_without_bodies,
15
+ },
16
+ class: {
17
+ api: class_api_methods,
18
+ inherited: class_inherited_methods,
19
+ other: class_other_methods,
20
+ without_bodies: class_without_bodies,
21
+ },
22
+ }
23
+ end
24
+
25
+ def instance_api_methods
26
+ Set.new class_hatchery.api_method_names
27
+ end
28
+
29
+ def instance_inherited_methods
30
+ Set.new surrogate_class.instance_methods - surrogate_class.instance_methods(false)
31
+ end
32
+
33
+ def instance_other_methods
34
+ Set.new(surrogate_class.instance_methods false) - instance_api_methods
35
+ end
36
+
37
+ def instance_without_bodies
38
+ Set.new class_hatchery.api_method_names.reject { |name| class_hatchery.api_method_for name }
39
+ end
40
+
41
+ def class_api_methods
42
+ Set.new singleton_class_hatchery.api_method_names
43
+ end
44
+
45
+ def class_inherited_methods
46
+ Set.new surrogate_class.singleton_class.instance_methods - surrogate_class.singleton_class.instance_methods(false)
47
+ end
48
+
49
+ def class_other_methods
50
+ Set.new(surrogate_class.singleton_class.instance_methods false) - class_api_methods - class_inherited_methods
51
+ end
52
+
53
+ def class_without_bodies
54
+ Set.new singleton_class_hatchery.api_method_names.reject { |name| singleton_class_hatchery.api_method_for name }
55
+ end
56
+
57
+ def class_hatchery
58
+ @class_hatchery ||= surrogate_class.instance_variable_get :@hatchery
59
+ end
60
+
61
+ def singleton_class_hatchery
62
+ @singleton_class_hatchery ||= surrogate_class.singleton_class.instance_variable_get :@hatchery
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,15 @@
1
+ class Surrogate
2
+
3
+ # Utilities for reflecting on surrogate instances
4
+ #
5
+ # Primarily it exists to avoid having to pollute the surrogate with reflection methods
6
+ class SurrogateInstanceReflector < Struct.new(:surrogate)
7
+ def invocations(method_name)
8
+ hatchling.invocations(method_name)
9
+ end
10
+
11
+ def hatchling
12
+ @hatchling ||= surrogate.instance_variable_get :@hatchling
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  class Surrogate
2
- VERSION = "0.5.5"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -116,7 +116,7 @@ describe Surrogate do
116
116
 
117
117
  # real user must have all of mock user's methods to be substitutable
118
118
  substitutable_real_user_class = Class.new do
119
- def self.find() end
119
+ def self.find(id) end
120
120
  def initialize(id) end
121
121
  def id() end
122
122
  def name() end
@@ -1,13 +1,25 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'define' do
4
+ def class_method_names(surrogate)
5
+ Surrogate::SurrogateClassReflector.new(surrogate).class_api_methods
6
+ end
7
+
8
+ def instance_method_names(surrogate)
9
+ Surrogate::SurrogateClassReflector.new(surrogate).instance_api_methods
10
+ end
11
+
12
+ def invocations(surrogate, method_name)
13
+ Surrogate::SurrogateInstanceReflector.new(surrogate).invocations(method_name)
14
+ end
15
+
4
16
  let(:mocked_class) { Surrogate.endow Class.new }
5
17
  let(:instance) { mocked_class.new }
6
18
 
7
19
  describe 'in the block' do
8
20
  it 'is an api method for the class' do
9
21
  pristine_klass = Class.new { Surrogate.endow(self) { define :find } }
10
- pristine_klass.singleton_class.api_method_names.should == [:find]
22
+ class_method_names(pristine_klass).should == Set[:find]
11
23
  end
12
24
  end
13
25
 
@@ -15,18 +27,11 @@ describe 'define' do
15
27
  describe 'out of the block' do
16
28
  it 'is an api method for the instance' do
17
29
  mocked_class.define :book
18
- mocked_class.api_method_names.should == [:book]
30
+ instance_method_names(mocked_class).should == Set[:book]
19
31
  end
20
32
  end
21
33
 
22
34
  describe 'definition default block invocation' do
23
- xit "something about raising an error if arity is wrong" do
24
- mocked_class.define(:a) { |arg| 1 }
25
- mocked_class.new.a.should == 1
26
- mocked_class.new.a(2).should == 1
27
- mocked_class.new.a(3).should == 1
28
- end
29
-
30
35
  it "is passed the arguments" do
31
36
  arg = nil
32
37
  mocked_class.define(:meth) { |inner_arg| arg = inner_arg }.new.meth(1212)
@@ -45,7 +50,6 @@ describe 'define' do
45
50
  end
46
51
 
47
52
  describe 'declaring the behaviour' do
48
-
49
53
  describe 'for verbs' do
50
54
  before { mocked_class.define :wink }
51
55
 
@@ -144,11 +148,11 @@ describe 'define' do
144
148
 
145
149
 
146
150
  context 'the api method' do
147
- it 'takes any number of arguments' do
148
- mocked_class.define(:meth) { 1 }
149
- mocked_class.new.meth.should == 1
151
+ it 'has the same arity as the method' do
152
+ mocked_class.define(:meth) { |a| a }
150
153
  mocked_class.new.meth(1).should == 1
151
- mocked_class.new.meth(1, 2).should == 1
154
+ expect { mocked_class.new.meth }.to raise_error ArgumentError, /0 for 1/
155
+ expect { mocked_class.new.meth 1, 2 }.to raise_error ArgumentError, /2 for 1/
152
156
  end
153
157
 
154
158
  it 'raises an UnpreparedMethodError when it has no default block' do
@@ -177,58 +181,26 @@ describe 'define' do
177
181
  mocked.meth.should == 123
178
182
  end
179
183
 
180
- describe 'initialization' do
181
- specify 'api methods can be an initialize method' do
182
- mocked_class.define(:initialize) { @abc = 123 }
183
- mocked_class.new.instance_variable_get(:@abc).should == 123
184
- end
185
-
186
- specify 'initialize exsits even if error is raised' do
187
- mocked_class.define(:initialize) { raise "simulate runtime error" }
188
- expect { mocked_class.new }.to raise_error(RuntimeError, /simulate/)
189
- expect { mocked_class.new }.to raise_error(RuntimeError, /simulate/)
190
- end
191
-
192
- specify 'receives args' do
193
- mocked_class.define(:initialize) { |num1, num2| @num = num1 + num2 }
194
- mocked_class.new(25, 75).instance_variable_get(:@num).should == 100
195
- end
196
-
197
- specify 'even works with inheritance' do
198
- superclass = Class.new
199
- superclass.send(:define_method, :initialize) { @a = 1 }
200
- subclass = Surrogate.endow Class.new superclass
201
- subclass.define :abc
202
- subclass.new.instance_variable_get(:@a).should == 1
203
- end
184
+ it 'raises arity errors, even if the value is overridden' do
185
+ mocked_class.define(:meth) { }
186
+ mocked = mocked_class.new
187
+ mocked.instance_variable_set :@meth, "abc"
188
+ expect { mocked.meth "extra", "args" }.to raise_error ArgumentError, /wrong number of arguments \(2 for 0\)/
189
+ end
204
190
 
205
- context 'when not an api method' do
206
- it 'respects arity (this is probably 1.9.3 only)' do
207
- expect { mocked_class.new 1 }.to raise_error ArgumentError, 'wrong number of arguments(1 for 0)'
208
- end
191
+ it 'does not raise arity errors, when there is no default block and the value is overridden' do
192
+ mocked_class.define :meth
193
+ mocked = mocked_class.new
194
+ mocked.instance_variable_set :@meth, "abc"
195
+ mocked.meth 1, 2, 3
196
+ end
209
197
 
210
- describe 'invocations are recorded anyway' do
211
- specify 'even when initialize is defined after surrogate block' do
212
- klass = Class.new do
213
- Surrogate.endow self
214
- def initialize(a) @a = a end
215
- end
216
- klass.new(1).should have_been_initialized_with 1
217
- klass.new(1).instance_variable_get(:@a).should == 1
218
- end
219
-
220
- specify 'even when initialize is defined before surrogate block' do
221
- klass = Class.new do
222
- def initialize(a) @a = a end
223
- Surrogate.endow self
224
- end
225
- klass.new(1).should have_been_initialized_with 1
226
- klass.new(1).instance_variable_get(:@a).should == 1
227
- end
228
- end
229
- end
198
+ it 'can make #initialize an api method' do
199
+ mocked_class.define(:initialize) { @abc = 123 }
200
+ mocked_class.new.instance_variable_get(:@abc).should == 123
230
201
  end
231
202
 
203
+
232
204
  describe 'it takes a block whos return value will be used as the default' do
233
205
  specify 'the block is instance evaled' do
234
206
  mocked_class.define(:meth) { self }
@@ -245,32 +217,32 @@ describe 'define' do
245
217
  end
246
218
 
247
219
  it 'remembers what it was invoked with' do
248
- mocked_class.define(:meth) { nil }
220
+ mocked_class.define(:meth) { |*| nil }
249
221
  mock = mocked_class.new
250
222
  mock.meth 1
251
223
  mock.meth 1, 2
252
- mock.invocations(:meth).should == [Surrogate::Invocation.new([1]), Surrogate::Invocation.new([1, 2])]
224
+ invocations(mock, :meth).should == [Surrogate::Invocation.new([1]), Surrogate::Invocation.new([1, 2])]
253
225
 
254
226
  val = 0
255
227
  mock.meth(1, 2) { val = 3 }
256
- expect { mock.invocations(:meth).last.block.call }.to change { val }.from(0).to(3)
228
+ expect { invocations(mock, :meth).last.block.call }.to change { val }.from(0).to(3)
257
229
  end
258
230
 
259
231
  it 'raises an error if asked about invocations for api methods it does not know' do
260
232
  mocked_class.define :meth1
261
233
  mocked_class.define :meth2
262
234
  mock = mocked_class.new
263
- expect { mock.invocations(:meth1) }.to_not raise_error
264
- expect { mock.invocations(:meth3) }.to raise_error Surrogate::UnknownMethod, /doesn't know "meth3", only knows "initialize", "meth1", "meth2"/
235
+ expect { invocations mock, :meth1 }.to_not raise_error
236
+ expect { invocations mock, :meth3 }.to raise_error Surrogate::UnknownMethod, /doesn't know "meth3", only knows "meth1", "meth2"/
265
237
  end
266
238
  end
267
239
 
268
240
 
269
241
  describe 'clone' do
270
- it 'a repetition or further performance of the klass' do
242
+ example 'acceptance spec' do
271
243
  pristine_klass = Class.new do
272
244
  Surrogate.endow self do
273
- define(:find) { 123 }
245
+ define(:find) { |n| 123 }
274
246
  define(:bind) { 'abc' }
275
247
  end
276
248
  define(:peat) { true }
@@ -286,7 +258,7 @@ describe 'define' do
286
258
  klass2 = pristine_klass.clone
287
259
  klass2.will_find 456
288
260
  klass2.find(2).should == 456
289
- klass1.find.should == 123
261
+ klass1.find(3).should == 123
290
262
 
291
263
  klass1.should have_been_told_to(:find).with(1)
292
264
  klass2.should have_been_told_to(:find).with(2)
@@ -301,15 +273,17 @@ describe 'define' do
301
273
  superclass = Surrogate.endow Class.new
302
274
  superclass.clone.new.should be_a_kind_of superclass
303
275
  end
304
- end
305
276
 
306
- describe '#api_method_names' do
307
- it 'returns the names of the api methods as symbols' do
308
- mocked_class = Class.new do
309
- Surrogate.endow self
310
- define :abc
277
+ describe '.name' do
278
+ it 'is nil for anonymous classes' do
279
+ Surrogate.endow(Class.new).clone.name.should be_nil
280
+ end
281
+
282
+ it "is the class's name suffixed with '.clone' for named classes" do
283
+ klass = Surrogate.endow(Class.new)
284
+ self.class.const_set 'Xyz', klass
285
+ klass.clone.name.should == self.class.name + '::Xyz.clone'
311
286
  end
312
- mocked_class.api_method_names.should == [:abc]
313
287
  end
314
288
  end
315
289
  end
@@ -52,3 +52,134 @@ describe '.last_instance' do
52
52
  end
53
53
  end
54
54
  end
55
+
56
+
57
+ describe 'inspect methods' do
58
+ context 'on the class' do
59
+ context 'when anonymous identifies itself as an anonymous surrogate and lists three each of class and instance methods, alphabetically' do
60
+ example 'no class methods' do
61
+ Surrogate.endow(Class.new).inspect.should == 'AnonymousSurrogate(no api)'
62
+ end
63
+
64
+ example 'one class method' do
65
+ klass = Surrogate.endow(Class.new) { define :cmeth }
66
+ klass.inspect.should == 'AnonymousSurrogate(Class: cmeth)'
67
+ end
68
+
69
+ example 'more than three class methods' do
70
+ Surrogate.endow(Class.new) do
71
+ define :cmethd
72
+ define :cmethc
73
+ define :cmetha
74
+ define :cmethb
75
+ end.inspect.should == 'AnonymousSurrogate(Class: cmetha cmethb cmethc ...)'
76
+ end
77
+
78
+ example 'one instance method' do
79
+ Surrogate.endow(Class.new).define(:imeth).inspect.should == 'AnonymousSurrogate(Instance: imeth)'
80
+ end
81
+
82
+ example 'more than three instance methods' do
83
+ klass = Surrogate.endow Class.new
84
+ klass.define :imethd
85
+ klass.define :imethc
86
+ klass.define :imetha
87
+ klass.define :imethb
88
+ klass.inspect.should == 'AnonymousSurrogate(Instance: imetha imethb imethc ...)'
89
+ end
90
+
91
+ example 'one of each' do
92
+ Surrogate.endow(Class.new) { define :cmeth }.define(:imeth).inspect.should == 'AnonymousSurrogate(Class: cmeth, Instance: imeth)'
93
+ end
94
+
95
+ example 'more than three of each' do
96
+ klass = Surrogate.endow Class.new do
97
+ define :cmethd
98
+ define :cmethc
99
+ define :cmetha
100
+ define :cmethb
101
+ end
102
+ klass.define :imethd
103
+ klass.define :imethc
104
+ klass.define :imetha
105
+ klass.define :imethb
106
+ klass.inspect.should == 'AnonymousSurrogate(Class: cmetha cmethb cmethc ..., Instance: imetha imethb imethc ...)'
107
+ end
108
+ end
109
+
110
+ context 'when the surrogate has a name (e.g. assigned to a constant)' do
111
+ it 'inspects to the name of the constant' do
112
+ klass = Surrogate.endow(Class.new)
113
+ self.class.const_set 'Abc', klass
114
+ klass.inspect.should == self.class.name << '::Abc'
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'on a clone of the surrogate' do
120
+ context 'when the surrogate has a name (e.g. assigned to a constant)' do
121
+ it 'inspects to the name of the constant, cloned' do
122
+ klass = Surrogate.endow(Class.new)
123
+ self.class.const_set 'Abc', klass
124
+ klass.clone.inspect.should == self.class.name << '::Abc.clone'
125
+ end
126
+ end
127
+ end
128
+
129
+ # eventually these should maybe show unique state (expectations/invocations) but for now, this is better than what was there before
130
+ context 'on the instance' do
131
+ context 'when anonymous' do
132
+ it 'identifies itself as an anonymous surrogate and lists three of its methods, alphabetically' do
133
+ klass = Surrogate.endow Class.new
134
+ klass.new.inspect.should == '#<AnonymousSurrogate: no api>'
135
+ klass.define :imethb
136
+ klass.new.inspect.should == '#<AnonymousSurrogate: imethb>'
137
+ klass.define :imetha
138
+ klass.define :imethd
139
+ klass.new.inspect.should == '#<AnonymousSurrogate: imetha imethb imethd>'
140
+ klass.define :imethc
141
+ klass.new.inspect.should == '#<AnonymousSurrogate: imetha imethb imethc ...>'
142
+ end
143
+ end
144
+
145
+ context 'when a clone of an anonymous surrogate' do
146
+ it 'looks the same as any other anonymous surrogate' do
147
+ klass = Surrogate.endow Class.new
148
+ klass.new.inspect.should == '#<AnonymousSurrogate: no api>'
149
+ end
150
+ end
151
+
152
+ context 'when its class has a name (e.g. for a constant)' do
153
+ it 'identifies itself as an instance of the constant and lists three of its methods, alphabetically' do
154
+ klass = Surrogate.endow Class.new
155
+ self.class.const_set 'Abc', klass
156
+ klass.stub name: 'Abc'
157
+ klass.new.inspect.should == "#<Abc: no api>"
158
+ klass.define :imethb
159
+ klass.new.inspect.should == "#<Abc: imethb>"
160
+ klass.define :imetha
161
+ klass.define :imethd
162
+ klass.new.inspect.should == "#<Abc: imetha imethb imethd>"
163
+ klass.define :imethc
164
+ klass.new.inspect.should == "#<Abc: imetha imethb imethc ...>"
165
+ end
166
+ end
167
+ end
168
+
169
+ context 'on an instance of a surrogate clone' do
170
+ context 'when its class has a name (e.g. for a constant)' do
171
+ it 'identifies itself as an instance of the clone of the constant and lists three of its methods, alphabetically' do
172
+ klass = Surrogate.endow Class.new
173
+ self.class.const_set 'Abc', klass
174
+ klass.clone.new.inspect.should == "#<#{self.class}::Abc.clone: no api>"
175
+ klass.define :imethb
176
+ klass.clone.new.inspect.should == "#<#{self.class}::Abc.clone: imethb>"
177
+ klass.define :imetha
178
+ klass.define :imethd
179
+ klass.clone.new.inspect.should == "#<#{self.class}::Abc.clone: imetha imethb imethd>"
180
+ klass.define :imethc
181
+ klass.clone.new.inspect.should == "#<#{self.class}::Abc.clone: imetha imethb imethc ...>"
182
+ end
183
+ end
184
+ end
185
+ end