surrogate 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/Rakefile +5 -1
- data/Readme.md +28 -1
- data/Readme.md.mountain_berry_fields +558 -0
- data/lib/surrogate/version.rb +1 -1
- data/surrogate.gemspec +5 -1
- metadata +53 -7
data/.travis.yml
ADDED
data/Rakefile
CHANGED
data/Readme.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
|
2
|
+
|
1
3
|
About
|
2
4
|
=====
|
3
5
|
|
@@ -13,6 +15,27 @@ discouraged at this time. If you do want to do this (e.g. to make an interface f
|
|
13
15
|
let me know, and I'll inform you / fork your gem and help update it, for any breaking changes
|
14
16
|
that I introduce.
|
15
17
|
|
18
|
+
New Syntax
|
19
|
+
==========
|
20
|
+
|
21
|
+
Recently (v0.5.1), a new syntax was added:
|
22
|
+
|
23
|
+
<table>
|
24
|
+
<tr><th>Old</th><th>New</th></tr>
|
25
|
+
<tr><td>.should have_been_told_to</td><td>.was told_to</td></tr>
|
26
|
+
<tr><td>.should have_been_asked_for_its</td><td>.was asked_for</td></tr>
|
27
|
+
<tr><td>.should have_been_asked_if</td><td>.was asked_if</td></tr>
|
28
|
+
<tr><td>.should have_been_initialized_with</td><td>.was initialized_with</td></tr>
|
29
|
+
</table>
|
30
|
+
|
31
|
+
If you want to switch over, here is a shell script that should get you pretty far:
|
32
|
+
|
33
|
+
find spec -type file |
|
34
|
+
xargs ruby -p -i.old_syntax \
|
35
|
+
-e 'gsub /should(_not)?(\s+)have_been_told_to/, "was\\1\\2told_to"' \
|
36
|
+
-e 'gsub /should(_not)?(\s+)have_been_asked_(if|for)(_its)?/, "was\\1\\2asked_\\3"' \
|
37
|
+
-e 'gsub /should(_not)(\s+)have_been_initialized_with/, "was\\1\\2initialized_with"' \
|
38
|
+
|
16
39
|
|
17
40
|
Features
|
18
41
|
========
|
@@ -200,7 +223,7 @@ mp3.info :title
|
|
200
223
|
mp3.should have_been_asked_for_its(:info).with(:title)
|
201
224
|
```
|
202
225
|
|
203
|
-
Supports RSpec's `no_args`
|
226
|
+
Supports RSpec's matchers (`no_args`, `hash_including`, etc)
|
204
227
|
|
205
228
|
```ruby
|
206
229
|
mp3.info
|
@@ -422,6 +445,8 @@ Special Thanks
|
|
422
445
|
TODO
|
423
446
|
----
|
424
447
|
|
448
|
+
* Remove dependency on all of RSpec and only depend on rspec-core, then have AC tests for the other shit
|
449
|
+
* Move surrogates to be first class and defined in the classes that use them.
|
425
450
|
* Add proper failure messages for block invocations
|
426
451
|
* Add a better explanation for motivations
|
427
452
|
* 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)
|
@@ -430,6 +455,8 @@ TODO
|
|
430
455
|
* extract surrogate/rspec into its own gem
|
431
456
|
* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
|
432
457
|
* Add a last_instance option so you don't have to track it explicitly
|
458
|
+
* make substitutability matcher go either way
|
459
|
+
* make substitutability matcher not care whether either are surrogates
|
433
460
|
|
434
461
|
|
435
462
|
Future Features
|
@@ -0,0 +1,558 @@
|
|
1
|
+
<% setup do %>
|
2
|
+
$LOAD_PATH.unshift '../lib', __FILE__
|
3
|
+
require 'surrogate'
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<% context 'generic it block' do %>
|
7
|
+
describe 'whatever' do
|
8
|
+
it 'does something' do
|
9
|
+
__CODE__
|
10
|
+
end
|
11
|
+
end
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
About
|
15
|
+
=====
|
16
|
+
|
17
|
+
Handrolling mocks is the best, but involves more overhead than necessary, and usually has less helpful
|
18
|
+
error messages. Surrogate addresses this by endowing your objects with common things that most mocks need.
|
19
|
+
Currently it is only integrated with RSpec.
|
20
|
+
|
21
|
+
This codebase should be considered highly volatile until 1.0 release. The outer interface should be
|
22
|
+
fairly stable, with each 0.a.b version having backwards compatibility for any changes to b (ie
|
23
|
+
only refactorings and new features), and possible interface changes (though probably minimal)
|
24
|
+
for changes to a. Depending on the internals of the code (anything not shown in the readme) is
|
25
|
+
discouraged at this time. If you do want to do this (e.g. to make an interface for test/unit)
|
26
|
+
let me know, and I'll inform you / fork your gem and help update it, for any breaking changes
|
27
|
+
that I introduce.
|
28
|
+
|
29
|
+
New Syntax
|
30
|
+
==========
|
31
|
+
|
32
|
+
Recently (v0.5.1), a new syntax was added:
|
33
|
+
|
34
|
+
<table>
|
35
|
+
<tr><th>Old</th><th>New</th></tr>
|
36
|
+
<tr><td>.should have_been_told_to</td><td>.was told_to</td></tr>
|
37
|
+
<tr><td>.should have_been_asked_for_its</td><td>.was asked_for</td></tr>
|
38
|
+
<tr><td>.should have_been_asked_if</td><td>.was asked_if</td></tr>
|
39
|
+
<tr><td>.should have_been_initialized_with</td><td>.was initialized_with</td></tr>
|
40
|
+
</table>
|
41
|
+
|
42
|
+
If you want to switch over, here is a shell script that should get you pretty far:
|
43
|
+
|
44
|
+
find spec -type file |
|
45
|
+
xargs ruby -p -i.old_syntax \
|
46
|
+
-e 'gsub /should(_not)?(\s+)have_been_told_to/, "was\\1\\2told_to"' \
|
47
|
+
-e 'gsub /should(_not)?(\s+)have_been_asked_(if|for)(_its)?/, "was\\1\\2asked_\\3"' \
|
48
|
+
-e 'gsub /should(_not)(\s+)have_been_initialized_with/, "was\\1\\2initialized_with"' \
|
49
|
+
|
50
|
+
|
51
|
+
Features
|
52
|
+
========
|
53
|
+
|
54
|
+
* Declarative syntax
|
55
|
+
* Support default values
|
56
|
+
* Easily override values
|
57
|
+
* RSpec matchers for asserting what happend (what was invoked, with what args, how many times)
|
58
|
+
* RSpec matchers for asserting the Mock's interface matches the real object
|
59
|
+
* Support for exceptions
|
60
|
+
* Queue return values
|
61
|
+
* Initialization information is always recorded
|
62
|
+
|
63
|
+
|
64
|
+
Usage
|
65
|
+
=====
|
66
|
+
|
67
|
+
**Endow** a class with surrogate abilities
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
<% test 'endowing', with: :magic_comments do %>
|
71
|
+
class Mock
|
72
|
+
Surrogate.endow self
|
73
|
+
end
|
74
|
+
<% end %>
|
75
|
+
```
|
76
|
+
|
77
|
+
Define a **class method** by using `define` in the block when endowing your class.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
<% test 'class api method', with: :magic_comments do %>
|
81
|
+
class MockClient
|
82
|
+
Surrogate.endow self do
|
83
|
+
define(:default_url) { 'http://example.com' }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
MockClient.default_url # => "http://example.com"
|
88
|
+
<% end %>
|
89
|
+
```
|
90
|
+
|
91
|
+
Define an **instance method** by using `define` outside the block after endowing your class.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
<% test 'instance api method', with: :magic_comments do %>
|
95
|
+
class MockClient
|
96
|
+
Surrogate.endow self
|
97
|
+
define(:request) { ['result1', 'result2'] }
|
98
|
+
end
|
99
|
+
|
100
|
+
MockClient.new.request # => ["result1", "result2"]
|
101
|
+
<% end %>
|
102
|
+
```
|
103
|
+
|
104
|
+
If you care about the **arguments**, your block can receive them.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
<% test 'api method with arguments', with: :magic_comments do %>
|
108
|
+
class MockClient
|
109
|
+
Surrogate.endow self
|
110
|
+
define(:request) { |limit| limit.times.map { |i| "result#{i.next}" } }
|
111
|
+
end
|
112
|
+
|
113
|
+
MockClient.new.request 3 # => ["result1", "result2", "result3"]
|
114
|
+
<% end %>
|
115
|
+
```
|
116
|
+
|
117
|
+
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)
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
<% test 'overriding default by setting the ivar', with: :magic_comments do %>
|
121
|
+
class MockClient
|
122
|
+
Surrogate.endow self
|
123
|
+
define(:initialize) { |id| @id, @connected_p = id, true }
|
124
|
+
define :id
|
125
|
+
define :connected?
|
126
|
+
end
|
127
|
+
MockClient.new(12).id # => 12
|
128
|
+
<% end %>
|
129
|
+
```
|
130
|
+
|
131
|
+
**Override defaults** with `will_<verb>` and `will_have_<noun>`
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
<% test 'overriding default by invoking the method', with: :magic_comments do %>
|
135
|
+
class MockMP3
|
136
|
+
Surrogate.endow self
|
137
|
+
define :play # defaults are optional, will raise error if invoked without being told what to do
|
138
|
+
define :info
|
139
|
+
end
|
140
|
+
|
141
|
+
mp3 = MockMP3.new
|
142
|
+
|
143
|
+
# verbs
|
144
|
+
mp3.will_play true
|
145
|
+
mp3.play # => true
|
146
|
+
|
147
|
+
# nouns
|
148
|
+
mp3.will_have_info artist: 'Symphony of Science', title: 'Children of Africa'
|
149
|
+
mp3.info # => {:artist=>"Symphony of Science", :title=>"Children of Africa"}
|
150
|
+
<% end %>
|
151
|
+
```
|
152
|
+
|
153
|
+
**Errors** get raised
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
<% test 'errors get raised', with: :magic_comments do %>
|
157
|
+
class MockClient
|
158
|
+
Surrogate.endow self
|
159
|
+
define :request
|
160
|
+
end
|
161
|
+
|
162
|
+
client = MockClient.new
|
163
|
+
client.will_have_request StandardError.new('Remote service unavailable')
|
164
|
+
|
165
|
+
begin
|
166
|
+
client.request
|
167
|
+
rescue StandardError => e
|
168
|
+
e # => #<StandardError: Remote service unavailable>
|
169
|
+
end
|
170
|
+
<% end %>
|
171
|
+
```
|
172
|
+
|
173
|
+
**Queue** up return values
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
<% test 'queue up return values', with: :magic_comments do %>
|
177
|
+
class MockPlayer
|
178
|
+
Surrogate.endow self
|
179
|
+
define(:move) { 20 }
|
180
|
+
end
|
181
|
+
|
182
|
+
player = MockPlayer.new
|
183
|
+
player.will_move 1, 9, 3
|
184
|
+
player.move # => 1
|
185
|
+
player.move # => 9
|
186
|
+
player.move # => 3
|
187
|
+
<% end %>
|
188
|
+
```
|
189
|
+
|
190
|
+
You can define **initialize**
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
<% test 'defining initialize', with: :magic_comments do %>
|
194
|
+
class MockUser
|
195
|
+
Surrogate.endow self do
|
196
|
+
define(:find) { |id| new id }
|
197
|
+
end
|
198
|
+
define(:initialize) { |id| @id = id }
|
199
|
+
define(:id) { @id }
|
200
|
+
end
|
201
|
+
|
202
|
+
user = MockUser.find 12
|
203
|
+
user.id # => 12
|
204
|
+
<% end %>
|
205
|
+
```
|
206
|
+
|
207
|
+
|
208
|
+
RSpec Integration
|
209
|
+
=================
|
210
|
+
|
211
|
+
Currently only integrated with RSpec, since that's what I use. It has some builtin matchers
|
212
|
+
for querying what happened.
|
213
|
+
|
214
|
+
Load the RSpec matchers.
|
215
|
+
|
216
|
+
<% setup do %>
|
217
|
+
require 'surrogate/rspec'
|
218
|
+
<% end %>
|
219
|
+
```ruby
|
220
|
+
require 'surrogate/rspec'
|
221
|
+
```
|
222
|
+
|
223
|
+
Nouns
|
224
|
+
-----
|
225
|
+
|
226
|
+
Given this mock and assuming the following examples happen within a spec
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
<% test "mock mp3 code shouldn't blow up", with: :magic_comments do %>
|
230
|
+
class MockMP3
|
231
|
+
Surrogate.endow self
|
232
|
+
define(:info) { 'some info' }
|
233
|
+
end
|
234
|
+
<% end %>
|
235
|
+
```
|
236
|
+
<%# need to figure out how to make context and setup blocks conditionally visible %>
|
237
|
+
<% context 'mp3 in spec' do %>
|
238
|
+
class MockMP3
|
239
|
+
Surrogate.endow self
|
240
|
+
define(:info) { 'some info' }
|
241
|
+
end
|
242
|
+
|
243
|
+
describe 'the example' do
|
244
|
+
let(:mp3) { MockMP3.new }
|
245
|
+
it 'executes' do
|
246
|
+
__CODE__
|
247
|
+
end
|
248
|
+
end
|
249
|
+
<% end %>
|
250
|
+
|
251
|
+
Check if **was invoked** with `have_been_asked_for_its`
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
<% test 'noun invocation', with: :rspec, context: 'mp3 in spec' do %>
|
255
|
+
mp3.should_not have_been_asked_for_its :info
|
256
|
+
mp3.info
|
257
|
+
mp3.should have_been_asked_for_its :info
|
258
|
+
<% end %>
|
259
|
+
```
|
260
|
+
|
261
|
+
Invocation **cardinality** by chaining `times(n)`
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
<% test 'noun cardinality', with: :rspec, context: 'mp3 in spec' do %>
|
265
|
+
mp3.info
|
266
|
+
mp3.info
|
267
|
+
mp3.should have_been_asked_for_its(:info).times(2)
|
268
|
+
<% end %>
|
269
|
+
```
|
270
|
+
|
271
|
+
Invocation **arguments** by chaining `with(args)`
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
<% test 'noun invocation with args', with: :rspec, context: 'mp3 in spec' do %>
|
275
|
+
mp3.info :title
|
276
|
+
mp3.should have_been_asked_for_its(:info).with(:title)
|
277
|
+
<% end %>
|
278
|
+
```
|
279
|
+
|
280
|
+
Supports RSpec's matchers (`no_args`, `hash_including`, etc)
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
<% test 'rspec matchers integration', with: :rspec, context: 'mp3 in spec' do %>
|
284
|
+
mp3.info
|
285
|
+
mp3.should have_been_asked_for_its(:info).with(no_args)
|
286
|
+
<% end %>
|
287
|
+
```
|
288
|
+
|
289
|
+
Cardinality of a specific set of args `with(args)` and `times(n)`
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
<% test 'times and with', with: :rspec, context: 'mp3 in spec' do %>
|
293
|
+
mp3.info :title
|
294
|
+
mp3.info :title
|
295
|
+
mp3.info :artist
|
296
|
+
mp3.should have_been_asked_for_its(:info).with(:title).times(2)
|
297
|
+
mp3.should have_been_asked_for_its(:info).with(:artist).times(1)
|
298
|
+
<% end %>
|
299
|
+
```
|
300
|
+
|
301
|
+
|
302
|
+
Verbs
|
303
|
+
-----
|
304
|
+
|
305
|
+
Given this mock and assuming the following examples happen within a spec
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
<% test 'mp3 that plays in in spec', with: :magic_comments do %>
|
309
|
+
class MockMP3
|
310
|
+
Surrogate.endow self
|
311
|
+
define(:play) { true }
|
312
|
+
end
|
313
|
+
<% end %>
|
314
|
+
```
|
315
|
+
<% context 'mp3 that plays in spec' do %>
|
316
|
+
class MockMP3
|
317
|
+
Surrogate.endow self
|
318
|
+
define(:play) { true }
|
319
|
+
end
|
320
|
+
|
321
|
+
describe 'the example' do
|
322
|
+
let(:mp3) { MockMP3.new }
|
323
|
+
it 'executes' do
|
324
|
+
__CODE__
|
325
|
+
end
|
326
|
+
end
|
327
|
+
<% end %>
|
328
|
+
|
329
|
+
Check if **was invoked** with `have_been_told_to`
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
<% test 'have_been_told_to', with: :rspec, context: 'mp3 that plays in spec' do %>
|
333
|
+
mp3.should_not have_been_told_to :play
|
334
|
+
mp3.play
|
335
|
+
mp3.should have_been_told_to :play
|
336
|
+
<% end %>
|
337
|
+
```
|
338
|
+
|
339
|
+
Also supports the same `with(args)` and `times(n)` that nouns have.
|
340
|
+
|
341
|
+
|
342
|
+
Initialization
|
343
|
+
--------------
|
344
|
+
|
345
|
+
Query with `have_been_initialized_with`, which is exactly the same as saying `have_been_told_to(:initialize).with(...)`
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
<% test 'initialization test', with: :rspec, context: 'generic it block' do %>
|
349
|
+
class MockUser
|
350
|
+
Surrogate.endow self
|
351
|
+
define(:initialize) { |id| @id = id }
|
352
|
+
define :id
|
353
|
+
end
|
354
|
+
user = MockUser.new 12
|
355
|
+
user.id.should == 12
|
356
|
+
user.should have_been_initialized_with 12
|
357
|
+
<% end %>
|
358
|
+
```
|
359
|
+
|
360
|
+
|
361
|
+
Predicates
|
362
|
+
----------
|
363
|
+
|
364
|
+
Query qith `have_been_asked_if`, all the same chainable methods from above apply.
|
365
|
+
|
366
|
+
```ruby
|
367
|
+
<% test 'initialization test', with: :rspec, context: 'generic it block' do %>
|
368
|
+
class MockUser
|
369
|
+
Surrogate.endow self
|
370
|
+
define(:admin?) { false }
|
371
|
+
end
|
372
|
+
|
373
|
+
user = MockUser.new
|
374
|
+
user.should_not be_admin
|
375
|
+
user.will_have_admin? true
|
376
|
+
user.should be_admin
|
377
|
+
user.should have_been_asked_if(:admin?).times(2)
|
378
|
+
<% end %>
|
379
|
+
```
|
380
|
+
|
381
|
+
|
382
|
+
class MockUser
|
383
|
+
|
384
|
+
Substitutability
|
385
|
+
----------------
|
386
|
+
|
387
|
+
After you've implemented the real version of your mock (assuming a [top-down](http://vimeo.com/31267109) style of development),
|
388
|
+
how do you prevent your real object from getting out of synch with your mock?
|
389
|
+
|
390
|
+
Assert that your mock has the **same interface** as your real class.
|
391
|
+
This will fail if the mock inherits methods which are not on the real class. It will also fail
|
392
|
+
if the real class has any methods which have not been defined on the mock or inherited by the mock.
|
393
|
+
|
394
|
+
Presently, it will ignore methods defined directly in the mock (as it adds quite a few of its own methods,
|
395
|
+
and generally considers them to be helpers). In a future version, you will be able to tell it to treat other methods
|
396
|
+
as part of the API (will fail if they don't match, and maybe record their values).
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
<% test 'substitutability example', with: :rspec, context: 'generic it block' do %>
|
400
|
+
class User
|
401
|
+
def initialize(id)end
|
402
|
+
def id()end
|
403
|
+
end
|
404
|
+
|
405
|
+
class MockUser
|
406
|
+
Surrogate.endow self
|
407
|
+
define(:initialize) { |id| @id = id }
|
408
|
+
define :id
|
409
|
+
end
|
410
|
+
|
411
|
+
# they are the same
|
412
|
+
MockUser.should substitute_for User
|
413
|
+
|
414
|
+
# mock has extra method
|
415
|
+
MockUser.define :name
|
416
|
+
MockUser.should_not substitute_for User
|
417
|
+
|
418
|
+
# the same again via inheritance
|
419
|
+
class UserWithName < User
|
420
|
+
def name()end
|
421
|
+
end
|
422
|
+
MockUser.should substitute_for UserWithName
|
423
|
+
|
424
|
+
# real class has extra methods
|
425
|
+
class UserWithNameAndAddress < UserWithName
|
426
|
+
def address()end
|
427
|
+
end
|
428
|
+
MockUser.should_not substitute_for UserWithNameAndAddress
|
429
|
+
<% end %>
|
430
|
+
```
|
431
|
+
|
432
|
+
Sometimes you don't want to have to implement the entire interface.
|
433
|
+
In these cases, you can assert that the methods on the mock are a **subset**
|
434
|
+
of the methods on the real class.
|
435
|
+
|
436
|
+
```ruby
|
437
|
+
<% test 'subset substitutability example', with: :rspec, context: 'generic it block' do %>
|
438
|
+
class User
|
439
|
+
def initialize(id)end
|
440
|
+
def id()end
|
441
|
+
def name()end
|
442
|
+
end
|
443
|
+
|
444
|
+
class MockUser
|
445
|
+
Surrogate.endow self
|
446
|
+
define(:initialize) { |id| @id = id }
|
447
|
+
define :id
|
448
|
+
end
|
449
|
+
|
450
|
+
# doesn't matter that real user has a name as long as it has initialize and id
|
451
|
+
MockUser.should substitute_for User, subset: true
|
452
|
+
|
453
|
+
# but now it fails b/c it has no address
|
454
|
+
MockUser.define :address
|
455
|
+
MockUser.should_not substitute_for User, subset: true
|
456
|
+
<% end %>
|
457
|
+
```
|
458
|
+
|
459
|
+
|
460
|
+
Blocks
|
461
|
+
------
|
462
|
+
|
463
|
+
When your method is invoked with a block, you can make assertions about the block.
|
464
|
+
|
465
|
+
_Note: Right now, block error messages have not been addressed (which means they are probably confusing as shit)_
|
466
|
+
|
467
|
+
Before/after hooks (make assertions here)
|
468
|
+
|
469
|
+
```ruby
|
470
|
+
<% test 'block example', with: :rspec, context: 'generic it block' do %>
|
471
|
+
class MockService
|
472
|
+
Surrogate.endow self
|
473
|
+
define(:create) {}
|
474
|
+
end
|
475
|
+
|
476
|
+
describe 'something that creates a user through the service' do
|
477
|
+
let(:old_id) { 12 }
|
478
|
+
let(:new_id) { 123 }
|
479
|
+
|
480
|
+
it 'updates the user_id and returns the old_id' do
|
481
|
+
user_id = old_id
|
482
|
+
service = MockService.new
|
483
|
+
|
484
|
+
service.create do |user|
|
485
|
+
to_return = user_id
|
486
|
+
user_id = user[:id]
|
487
|
+
to_return
|
488
|
+
end
|
489
|
+
|
490
|
+
service.should have_been_told_to(:create).with { |block|
|
491
|
+
block.call_with({id: new_id}) # this will be given to the block
|
492
|
+
block.returns old_id # provide a return value, or a block that receives the return value (where you can make assertions)
|
493
|
+
block.before { user_id.should == old_id } # assertions about state of the world before the block is called
|
494
|
+
block.after { user_id.should == new_id } # assertions about the state of the world after the block is called
|
495
|
+
}
|
496
|
+
end
|
497
|
+
end
|
498
|
+
<% end %>
|
499
|
+
```
|
500
|
+
|
501
|
+
|
502
|
+
How do I introduce my mocks?
|
503
|
+
============================
|
504
|
+
|
505
|
+
This is known as dependency injection. There are many ways you can do this, you can pass the object into
|
506
|
+
the initializer, you can pass a factory to your class, you can give the class that depends on the mock a
|
507
|
+
setter and then override it whenever you feel it is necessary, you can use RSpec's `#stub` method to put
|
508
|
+
it into place.
|
509
|
+
|
510
|
+
Personally, I use [Deject](https://rubygems.org/gems/deject), another gem I wrote. For more on why I feel
|
511
|
+
it is a better solution than the above methods, see it's [readme](https://github.com/JoshCheek/deject/tree/938edc985c65358c074a7c7b7bbf18dc11e9450e#why-write-this).
|
512
|
+
|
513
|
+
|
514
|
+
But why write this?
|
515
|
+
===================
|
516
|
+
|
517
|
+
Need to put an explanation here soon. In the meantime, I wrote a [blog](http://blog.8thlight.com/josh-cheek/2011/11/28/three-reasons-to-roll-your-own-mocks.html) that touches on the reasons.
|
518
|
+
|
519
|
+
|
520
|
+
Special Thanks
|
521
|
+
==============
|
522
|
+
|
523
|
+
* [Kyle Hargraves](https://github.com/pd) for changing the name of his internal gem so that I could take Surrogate
|
524
|
+
* [David Chelimsky](http://blog.davidchelimsky.net/) for pairing with me to make Surrogate integrate better with RSpec
|
525
|
+
* [Corey Haines](http://coreyhaines.com/) for pairing on substitutability with me
|
526
|
+
* [Enova](http://www.enovafinancial.com/) for giving me time and motivation to work on this during Enova Labs.
|
527
|
+
* [8th Light](http://8thlight.com/) for giving me time to work on this during our weekly Wazas, and the general encouragement and interest
|
528
|
+
|
529
|
+
|
530
|
+
TODO
|
531
|
+
----
|
532
|
+
|
533
|
+
* Remove dependency on all of RSpec and only depend on rspec-core, then have AC tests for the other shit
|
534
|
+
* Move surrogates to be first class and defined in the classes that use them.
|
535
|
+
* Add proper failure messages for block invocations
|
536
|
+
* Add a better explanation for motivations
|
537
|
+
* 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)
|
538
|
+
* 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)?)
|
539
|
+
* config: rspec_mocks loaded, whether unprepared blocks should raise or just return nil
|
540
|
+
* extract surrogate/rspec into its own gem
|
541
|
+
* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
|
542
|
+
* Add a last_instance option so you don't have to track it explicitly
|
543
|
+
* make substitutability matcher go either way
|
544
|
+
* make substitutability matcher not care whether either are surrogates
|
545
|
+
|
546
|
+
|
547
|
+
Future Features
|
548
|
+
---------------
|
549
|
+
|
550
|
+
* figure out how to talk about callbacks like #on_success
|
551
|
+
* have some sort of reinitialization that can hook into setup/teardown steps of test suite
|
552
|
+
* Support arity checking as part of substitutability
|
553
|
+
* 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)
|
554
|
+
* ability to declare normal methods as being part of the API
|
555
|
+
* ability to declare a define that uses the overridden method as the body, but can still act like an api method
|
556
|
+
* assertions for order of invocations & methods
|
557
|
+
* class generator? (supports a top-down style of development for when you write your mocks before you write your implementations)
|
558
|
+
* deal with hard dependency on rspec-mocks
|
data/lib/surrogate/version.rb
CHANGED
data/surrogate.gemspec
CHANGED
@@ -20,5 +20,9 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
s.add_runtime_dependency 'bindable_block', '= 0.0.5.1'
|
22
22
|
|
23
|
-
s.add_development_dependency "rspec", '~> 2.
|
23
|
+
s.add_development_dependency "rspec", '~> 2.2'
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
s.add_development_dependency "mountain_berry_fields", "~> 1.0.2"
|
26
|
+
s.add_development_dependency "mountain_berry_fields-rspec", "~> 1.0.2"
|
27
|
+
s.add_development_dependency "mountain_berry_fields-magic_comments", "~> 1.0.1"
|
24
28
|
end
|
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.5.
|
4
|
+
version: 0.5.2
|
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-07-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bindable_block
|
16
|
-
requirement: &
|
16
|
+
requirement: &70330602486000 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - =
|
@@ -21,18 +21,62 @@ dependencies:
|
|
21
21
|
version: 0.0.5.1
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70330602486000
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70330602485480 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 2.
|
32
|
+
version: '2.2'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70330602485480
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70330602485100 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70330602485100
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: mountain_berry_fields
|
49
|
+
requirement: &70330602484560 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.2
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70330602484560
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: mountain_berry_fields-rspec
|
60
|
+
requirement: &70330602484020 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.0.2
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70330602484020
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mountain_berry_fields-magic_comments
|
71
|
+
requirement: &70330602483520 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.0.1
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70330602483520
|
36
80
|
description: Framework to aid in handrolling mock/spy objects.
|
37
81
|
email:
|
38
82
|
- josh.cheek@gmail.com
|
@@ -42,9 +86,11 @@ extra_rdoc_files: []
|
|
42
86
|
files:
|
43
87
|
- .gitignore
|
44
88
|
- .rvmrc
|
89
|
+
- .travis.yml
|
45
90
|
- Gemfile
|
46
91
|
- Rakefile
|
47
92
|
- Readme.md
|
93
|
+
- Readme.md.mountain_berry_fields
|
48
94
|
- lib/surrogate.rb
|
49
95
|
- lib/surrogate/api_comparer.rb
|
50
96
|
- lib/surrogate/endower.rb
|