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.
- data/.rvmrc +0 -3
- data/Changelog.md +21 -0
- data/Readme.md +64 -76
- data/Readme.md.mountain_berry_fields +70 -77
- data/gemfiles/rspec_mocks_2.11 +1 -1
- data/lib/surrogate.rb +1 -1
- data/lib/surrogate/api_comparer.rb +59 -61
- data/lib/surrogate/argument_errorizer.rb +43 -0
- data/lib/surrogate/endower.rb +41 -37
- data/lib/surrogate/hatchery.rb +6 -1
- data/lib/surrogate/hatchling.rb +5 -0
- data/lib/surrogate/method_definition.rb +55 -0
- data/lib/surrogate/porc_reflector.rb +43 -0
- data/lib/surrogate/rspec/invocation_matcher.rb +2 -1
- data/lib/surrogate/rspec/substitute_for.rb +23 -7
- data/lib/surrogate/rspec/with_filter.rb +0 -1
- data/lib/surrogate/surrogate_class_reflector.rb +65 -0
- data/lib/surrogate/surrogate_instance_reflector.rb +15 -0
- data/lib/surrogate/version.rb +1 -1
- data/spec/acceptance_spec.rb +1 -1
- data/spec/defining_api_methods_spec.rb +51 -77
- data/spec/other_shit_spec.rb +131 -0
- data/spec/rspec/block_support_spec.rb +2 -2
- data/spec/rspec/initialization_matcher_spec.rb +12 -4
- data/spec/rspec/rspec_mocks_integration_spec.rb +1 -1
- data/spec/rspec/substitute_for_spec.rb +90 -4
- data/spec/unit/api_comparer_spec.rb +120 -4
- data/spec/unit/argument_errorizer_spec.rb +50 -0
- data/surrogate.gemspec +0 -2
- data/todo +44 -0
- metadata +21 -24
- data/lib/surrogate/options.rb +0 -41
data/.rvmrc
CHANGED
data/Changelog.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
### 0.6.0
|
2
|
+
|
3
|
+
* Setting an override still requires the invoking code to call with the correct signature
|
4
|
+
* Remove `api_method_names` and `api_method_for` and `invocations` from surrogates
|
5
|
+
(might break your code if you relied on these, but they were never advertized, and no obvious reason to use them)
|
6
|
+
Instead use the reflectors: Surrogate::SurrogateClassReflector and Surrogate::SurrogateInstanceReflector
|
7
|
+
* BREAKING CHANGE - Substitutability can check argument "types". This is turned on by default
|
8
|
+
* Initialize is no longer implicitly recorded (This might break something, but I don't think this feature was ever advertized, so hopefully people don't depend on it).
|
9
|
+
* BREAKING CHANGE - API method signatures are enforced (if meth takes 1 arg, you must pass it 1 arg)
|
10
|
+
* The name of a clone is the name of the parent suffixed with '.clone', unless the parent is anonymous (not set to a const), then the name is nil.
|
11
|
+
* Inspect messages are shorter and more helpful
|
12
|
+
* Inspect messages on class clones mimic the parents
|
13
|
+
* Remove comment about the new syntax in the Readme. If you want to switch over, here is a shell script that should get you pretty far:
|
14
|
+
|
15
|
+
find spec -type file |
|
16
|
+
xargs ruby -p -i.old_syntax \
|
17
|
+
-e 'gsub /should(_not)?(\s+)have_been_told_to/, "was\\1\\2told_to"' \
|
18
|
+
-e 'gsub /should(_not)?(\s+)have_been_asked_(if|for)(_its)?/, "was\\1\\2asked_\\3"' \
|
19
|
+
-e 'gsub /should(_not)(\s+)have_been_initialized_with/, "was\\1\\2initialized_with"' \
|
20
|
+
|
21
|
+
|
data/Readme.md
CHANGED
@@ -17,39 +17,17 @@ discouraged at this time. If you do want to do this (e.g. to make an interface f
|
|
17
17
|
let me know, and I'll inform you / fork your gem and help update it, for any breaking changes
|
18
18
|
that I introduce.
|
19
19
|
|
20
|
-
New Syntax
|
21
|
-
==========
|
22
|
-
|
23
|
-
Recently (v0.5.1), a new syntax was added:
|
24
|
-
|
25
|
-
<table>
|
26
|
-
<tr><th>Old</th><th>New</th></tr>
|
27
|
-
<tr><td>.should have_been_told_to</td><td>.was told_to</td></tr>
|
28
|
-
<tr><td>.should have_been_asked_for_its</td><td>.was asked_for</td></tr>
|
29
|
-
<tr><td>.should have_been_asked_if</td><td>.was asked_if</td></tr>
|
30
|
-
<tr><td>.should have_been_initialized_with</td><td>.was initialized_with</td></tr>
|
31
|
-
</table>
|
32
|
-
|
33
|
-
If you want to switch over, here is a shell script that should get you pretty far:
|
34
|
-
|
35
|
-
find spec -type file |
|
36
|
-
xargs ruby -p -i.old_syntax \
|
37
|
-
-e 'gsub /should(_not)?(\s+)have_been_told_to/, "was\\1\\2told_to"' \
|
38
|
-
-e 'gsub /should(_not)?(\s+)have_been_asked_(if|for)(_its)?/, "was\\1\\2asked_\\3"' \
|
39
|
-
-e 'gsub /should(_not)(\s+)have_been_initialized_with/, "was\\1\\2initialized_with"' \
|
40
|
-
|
41
|
-
|
42
20
|
Features
|
43
21
|
========
|
44
22
|
|
45
23
|
* Declarative syntax
|
24
|
+
* Can compare signatures of mocks to signatures of the class being mocked
|
46
25
|
* Support default values
|
47
26
|
* Easily override values
|
48
27
|
* RSpec matchers for asserting what happend (what was invoked, with what args, how many times)
|
49
28
|
* RSpec matchers for asserting the Mock's interface matches the real object
|
50
29
|
* Support for exceptions
|
51
30
|
* Queue return values
|
52
|
-
* Initialization information is always recorded
|
53
31
|
|
54
32
|
|
55
33
|
Usage
|
@@ -86,7 +64,10 @@ end
|
|
86
64
|
MockClient.new.request # => ["result1", "result2"]
|
87
65
|
```
|
88
66
|
|
89
|
-
If you
|
67
|
+
If you expect **arguments**, your block should receive them.
|
68
|
+
This prevents the issues with dynamic mocks where arguments and parameters can diverge.
|
69
|
+
It may seem like more work when you have to write the arguments explicitly, but you only
|
70
|
+
need to do this once, and then you can be sure they match on each invocation.
|
90
71
|
|
91
72
|
```ruby
|
92
73
|
class MockClient
|
@@ -98,6 +79,7 @@ MockClient.new.request 3 # => ["result1", "result2", "result3"]
|
|
98
79
|
```
|
99
80
|
|
100
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)
|
82
|
+
Note that methods without bodies will not have their arguments checked, and will not be asserted against when comparing signatures.
|
101
83
|
|
102
84
|
```ruby
|
103
85
|
class MockClient
|
@@ -177,6 +159,21 @@ user = MockUser.find 12
|
|
177
159
|
user.id # => 12
|
178
160
|
```
|
179
161
|
|
162
|
+
Use **clone** to avoid altering state on the class.
|
163
|
+
```ruby
|
164
|
+
class MockUser
|
165
|
+
Surrogate.endow self do
|
166
|
+
define(:find) { new }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
user_class = MockUser.clone
|
171
|
+
user_class.find
|
172
|
+
user_class.was told_to :find
|
173
|
+
MockUser.was_not told_to :find
|
174
|
+
```
|
175
|
+
|
176
|
+
|
180
177
|
|
181
178
|
RSpec Integration
|
182
179
|
=================
|
@@ -190,6 +187,21 @@ Load the RSpec matchers.
|
|
190
187
|
require 'surrogate/rspec'
|
191
188
|
```
|
192
189
|
|
190
|
+
Last Instance
|
191
|
+
-------------
|
192
|
+
|
193
|
+
Access the last instance of a class
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
class MockMp3
|
197
|
+
Surrogate.endow self
|
198
|
+
end
|
199
|
+
|
200
|
+
mp3_class = MockMp3.clone # because you don't want to mutate the singleton
|
201
|
+
mp3 = mp3_class.new
|
202
|
+
mp3_class.last_instance.equal? mp3 # => true
|
203
|
+
```
|
204
|
+
|
193
205
|
Nouns
|
194
206
|
-----
|
195
207
|
|
@@ -198,16 +210,16 @@ Given this mock and assuming the following examples happen within a spec
|
|
198
210
|
```ruby
|
199
211
|
class MockMP3
|
200
212
|
Surrogate.endow self
|
201
|
-
define(:info) { 'some info' }
|
213
|
+
define(:info) { |song='Birds Will Sing Forever'| 'some info' }
|
202
214
|
end
|
203
215
|
```
|
204
216
|
|
205
|
-
Check if **was invoked** with `
|
217
|
+
Check if **was invoked** with `was asked_for` (`was` is an alias of `should`, and `asked_for` is a custom matcher)
|
206
218
|
|
207
219
|
```ruby
|
208
|
-
mp3.
|
220
|
+
mp3.was_not asked_for :info
|
209
221
|
mp3.info
|
210
|
-
mp3.
|
222
|
+
mp3.was asked_for :info
|
211
223
|
```
|
212
224
|
|
213
225
|
Invocation **cardinality** by chaining `times(n)`
|
@@ -215,21 +227,21 @@ Invocation **cardinality** by chaining `times(n)`
|
|
215
227
|
```ruby
|
216
228
|
mp3.info
|
217
229
|
mp3.info
|
218
|
-
mp3.
|
230
|
+
mp3.was asked_for(:info).times(2)
|
219
231
|
```
|
220
232
|
|
221
233
|
Invocation **arguments** by chaining `with(args)`
|
222
234
|
|
223
235
|
```ruby
|
224
236
|
mp3.info :title
|
225
|
-
mp3.
|
237
|
+
mp3.was asked_for(:info).with(:title)
|
226
238
|
```
|
227
239
|
|
228
240
|
Supports RSpec's matchers (`no_args`, `hash_including`, etc)
|
229
241
|
|
230
242
|
```ruby
|
231
243
|
mp3.info
|
232
|
-
mp3.
|
244
|
+
mp3.was asked_for(:info).with(no_args)
|
233
245
|
```
|
234
246
|
|
235
247
|
Cardinality of a specific set of args `with(args)` and `times(n)`
|
@@ -238,8 +250,8 @@ Cardinality of a specific set of args `with(args)` and `times(n)`
|
|
238
250
|
mp3.info :title
|
239
251
|
mp3.info :title
|
240
252
|
mp3.info :artist
|
241
|
-
mp3.
|
242
|
-
mp3.
|
253
|
+
mp3.was asked_for(:info).with(:title).times(2)
|
254
|
+
mp3.was asked_for(:info).with(:artist).times(1)
|
243
255
|
```
|
244
256
|
|
245
257
|
|
@@ -255,12 +267,12 @@ class MockMP3
|
|
255
267
|
end
|
256
268
|
```
|
257
269
|
|
258
|
-
Check if **was invoked** with `
|
270
|
+
Check if **was invoked** with `was told_to`
|
259
271
|
|
260
272
|
```ruby
|
261
|
-
mp3.
|
273
|
+
mp3.was_not told_to :play
|
262
274
|
mp3.play
|
263
|
-
mp3.
|
275
|
+
mp3.was told_to :play
|
264
276
|
```
|
265
277
|
|
266
278
|
Also supports the same `with(args)` and `times(n)` that nouns have.
|
@@ -269,7 +281,7 @@ Also supports the same `with(args)` and `times(n)` that nouns have.
|
|
269
281
|
Initialization
|
270
282
|
--------------
|
271
283
|
|
272
|
-
Query with `
|
284
|
+
Query with `was initialized_with`, which is exactly the same as saying `was told_to(:initialize).with(...)`
|
273
285
|
|
274
286
|
```ruby
|
275
287
|
class MockUser
|
@@ -279,14 +291,14 @@ class MockUser
|
|
279
291
|
end
|
280
292
|
user = MockUser.new 12
|
281
293
|
user.id.should == 12
|
282
|
-
user.
|
294
|
+
user.was initialized_with 12
|
283
295
|
```
|
284
296
|
|
285
297
|
|
286
298
|
Predicates
|
287
299
|
----------
|
288
300
|
|
289
|
-
Query qith `
|
301
|
+
Query qith `was asked_if`, all the same chainable methods from above apply.
|
290
302
|
|
291
303
|
```ruby
|
292
304
|
class MockUser
|
@@ -298,15 +310,15 @@ user = MockUser.new
|
|
298
310
|
user.should_not be_admin
|
299
311
|
user.will_have_admin? true
|
300
312
|
user.should be_admin
|
301
|
-
user.
|
313
|
+
user.was asked_if(:admin?).times(2)
|
302
314
|
```
|
303
315
|
|
304
316
|
|
305
|
-
class MockUser
|
306
|
-
|
307
317
|
Substitutability
|
308
318
|
----------------
|
309
319
|
|
320
|
+
Facets of substitutability: method existence, argument types, (and soon argument names)
|
321
|
+
|
310
322
|
After you've implemented the real version of your mock (assuming a [top-down](http://vimeo.com/31267109) style of development),
|
311
323
|
how do you prevent your real object from getting out of synch with your mock?
|
312
324
|
|
@@ -314,8 +326,8 @@ Assert that your mock has the **same interface** as your real class.
|
|
314
326
|
This will fail if the mock inherits methods which are not on the real class. It will also fail
|
315
327
|
if the real class has any methods which have not been defined on the mock or inherited by the mock.
|
316
328
|
|
317
|
-
Presently, it will ignore methods defined directly in the mock (as it
|
318
|
-
|
329
|
+
Presently, it will ignore methods defined directly in the mock (as it generally considers them to be helpers).
|
330
|
+
In a future version, you will be able to tell it to treat other methods
|
319
331
|
as part of the API (will fail if they don't match, and maybe record their values).
|
320
332
|
|
321
333
|
```ruby
|
@@ -348,6 +360,13 @@ class UserWithNameAndAddress < UserWithName
|
|
348
360
|
def address()end
|
349
361
|
end
|
350
362
|
MockUser.should_not substitute_for UserWithNameAndAddress
|
363
|
+
|
364
|
+
# signatures don't match
|
365
|
+
class UserWithWrongSignature
|
366
|
+
def initialize()end # no id
|
367
|
+
def id()end
|
368
|
+
end
|
369
|
+
MockUser.should_not substitute_for UserWithWrongSignature
|
351
370
|
```
|
352
371
|
|
353
372
|
Sometimes you don't want to have to implement the entire interface.
|
@@ -405,7 +424,7 @@ describe 'something that creates a user through the service' do
|
|
405
424
|
to_return
|
406
425
|
end
|
407
426
|
|
408
|
-
service.
|
427
|
+
service.was told_to(:create).with { |block|
|
409
428
|
block.call_with({id: new_id}) # this will be given to the block
|
410
429
|
block.returns old_id # provide a return value, or a block that receives the return value (where you can make assertions)
|
411
430
|
block.before { user_id.should == old_id } # assertions about state of the world before the block is called
|
@@ -442,34 +461,3 @@ Special Thanks
|
|
442
461
|
* [Corey Haines](http://coreyhaines.com/) for pairing on substitutability with me
|
443
462
|
* [Enova](http://www.enovafinancial.com/) for giving me time and motivation to work on this during Enova Labs.
|
444
463
|
* [8th Light](http://8thlight.com/) for giving me time to work on this during our weekly Wazas, and the general encouragement and interest
|
445
|
-
|
446
|
-
|
447
|
-
TODO
|
448
|
-
----
|
449
|
-
|
450
|
-
* Remove dependency on all of RSpec and only depend on rspec-core, then have AC tests for the other shit
|
451
|
-
* Add proper failure messages for block invocations
|
452
|
-
* Add a better explanation for motivations
|
453
|
-
* 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)
|
454
|
-
* 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)?)
|
455
|
-
* config: rspec_mocks loaded, whether unprepared blocks should raise or just return nil
|
456
|
-
* extract surrogate/rspec into its own gem
|
457
|
-
* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
|
458
|
-
* Add a last_instance option so you don't have to track it explicitly
|
459
|
-
* make substitutability matcher go either way
|
460
|
-
* make substitutability matcher not care whether either are surrogates
|
461
|
-
* Add support for operators
|
462
|
-
|
463
|
-
|
464
|
-
Future Features
|
465
|
-
---------------
|
466
|
-
|
467
|
-
* figure out how to talk about callbacks like #on_success
|
468
|
-
* have some sort of reinitialization that can hook into setup/teardown steps of test suite
|
469
|
-
* Support arity checking as part of substitutability
|
470
|
-
* 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)
|
471
|
-
* ability to declare normal methods as being part of the API
|
472
|
-
* ability to declare a define that uses the overridden method as the body, but can still act like an api method
|
473
|
-
* assertions for order of invocations & methods
|
474
|
-
* class generator? (supports a top-down style of development for when you write your mocks before you write your implementations)
|
475
|
-
* deal with hard dependency on rspec-mocks
|
@@ -6,6 +6,7 @@ require 'surrogate'
|
|
6
6
|
<% end %>
|
7
7
|
|
8
8
|
<% context 'generic it block' do %>
|
9
|
+
require 'surrogate/rspec'
|
9
10
|
describe 'whatever' do
|
10
11
|
it 'does something' do
|
11
12
|
__CODE__
|
@@ -28,39 +29,17 @@ discouraged at this time. If you do want to do this (e.g. to make an interface f
|
|
28
29
|
let me know, and I'll inform you / fork your gem and help update it, for any breaking changes
|
29
30
|
that I introduce.
|
30
31
|
|
31
|
-
New Syntax
|
32
|
-
==========
|
33
|
-
|
34
|
-
Recently (v0.5.1), a new syntax was added:
|
35
|
-
|
36
|
-
<table>
|
37
|
-
<tr><th>Old</th><th>New</th></tr>
|
38
|
-
<tr><td>.should have_been_told_to</td><td>.was told_to</td></tr>
|
39
|
-
<tr><td>.should have_been_asked_for_its</td><td>.was asked_for</td></tr>
|
40
|
-
<tr><td>.should have_been_asked_if</td><td>.was asked_if</td></tr>
|
41
|
-
<tr><td>.should have_been_initialized_with</td><td>.was initialized_with</td></tr>
|
42
|
-
</table>
|
43
|
-
|
44
|
-
If you want to switch over, here is a shell script that should get you pretty far:
|
45
|
-
|
46
|
-
find spec -type file |
|
47
|
-
xargs ruby -p -i.old_syntax \
|
48
|
-
-e 'gsub /should(_not)?(\s+)have_been_told_to/, "was\\1\\2told_to"' \
|
49
|
-
-e 'gsub /should(_not)?(\s+)have_been_asked_(if|for)(_its)?/, "was\\1\\2asked_\\3"' \
|
50
|
-
-e 'gsub /should(_not)(\s+)have_been_initialized_with/, "was\\1\\2initialized_with"' \
|
51
|
-
|
52
|
-
|
53
32
|
Features
|
54
33
|
========
|
55
34
|
|
56
35
|
* Declarative syntax
|
36
|
+
* Can compare signatures of mocks to signatures of the class being mocked
|
57
37
|
* Support default values
|
58
38
|
* Easily override values
|
59
39
|
* RSpec matchers for asserting what happend (what was invoked, with what args, how many times)
|
60
40
|
* RSpec matchers for asserting the Mock's interface matches the real object
|
61
41
|
* Support for exceptions
|
62
42
|
* Queue return values
|
63
|
-
* Initialization information is always recorded
|
64
43
|
|
65
44
|
|
66
45
|
Usage
|
@@ -103,7 +82,10 @@ MockClient.new.request # => ["result1", "result2"]
|
|
103
82
|
<% end %>
|
104
83
|
```
|
105
84
|
|
106
|
-
If you
|
85
|
+
If you expect **arguments**, your block should receive them.
|
86
|
+
This prevents the issues with dynamic mocks where arguments and parameters can diverge.
|
87
|
+
It may seem like more work when you have to write the arguments explicitly, but you only
|
88
|
+
need to do this once, and then you can be sure they match on each invocation.
|
107
89
|
|
108
90
|
```ruby
|
109
91
|
<% test 'api method with arguments', with: :magic_comments do %>
|
@@ -117,6 +99,7 @@ MockClient.new.request 3 # => ["result1", "result2", "result3"]
|
|
117
99
|
```
|
118
100
|
|
119
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)
|
102
|
+
Note that methods without bodies will not have their arguments checked, and will not be asserted against when comparing signatures.
|
120
103
|
|
121
104
|
```ruby
|
122
105
|
<% test 'overriding default by setting the ivar', with: :magic_comments do %>
|
@@ -206,6 +189,23 @@ user.id # => 12
|
|
206
189
|
<% end %>
|
207
190
|
```
|
208
191
|
|
192
|
+
Use **clone** to avoid altering state on the class.
|
193
|
+
```ruby
|
194
|
+
<% test 'clone to avoid mutating state', with: :rspec, context: 'generic it block' do %>
|
195
|
+
class MockUser
|
196
|
+
Surrogate.endow self do
|
197
|
+
define(:find) { new }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
user_class = MockUser.clone
|
202
|
+
user_class.find
|
203
|
+
user_class.was told_to :find
|
204
|
+
MockUser.was_not told_to :find
|
205
|
+
<% end %>
|
206
|
+
```
|
207
|
+
|
208
|
+
|
209
209
|
|
210
210
|
RSpec Integration
|
211
211
|
=================
|
@@ -222,6 +222,23 @@ require 'surrogate/rspec'
|
|
222
222
|
require 'surrogate/rspec'
|
223
223
|
```
|
224
224
|
|
225
|
+
Last Instance
|
226
|
+
-------------
|
227
|
+
|
228
|
+
Access the last instance of a class
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
<% test "last instance", with: :magic_comments do %>
|
232
|
+
class MockMp3
|
233
|
+
Surrogate.endow self
|
234
|
+
end
|
235
|
+
|
236
|
+
mp3_class = MockMp3.clone # because you don't want to mutate the singleton
|
237
|
+
mp3 = mp3_class.new
|
238
|
+
mp3_class.last_instance.equal? mp3 # => true
|
239
|
+
<% end %>
|
240
|
+
```
|
241
|
+
|
225
242
|
Nouns
|
226
243
|
-----
|
227
244
|
|
@@ -231,7 +248,7 @@ Given this mock and assuming the following examples happen within a spec
|
|
231
248
|
<% test "mock mp3 code shouldn't blow up", with: :magic_comments do %>
|
232
249
|
class MockMP3
|
233
250
|
Surrogate.endow self
|
234
|
-
define(:info) { 'some info' }
|
251
|
+
define(:info) { |song='Birds Will Sing Forever'| 'some info' }
|
235
252
|
end
|
236
253
|
<% end %>
|
237
254
|
```
|
@@ -239,7 +256,7 @@ end
|
|
239
256
|
<% context 'mp3 in spec' do %>
|
240
257
|
class MockMP3
|
241
258
|
Surrogate.endow self
|
242
|
-
define(:info) { 'some info' }
|
259
|
+
define(:info) { |song='Birds Will Sing Forever'| 'some info' }
|
243
260
|
end
|
244
261
|
|
245
262
|
describe 'the example' do
|
@@ -250,13 +267,13 @@ describe 'the example' do
|
|
250
267
|
end
|
251
268
|
<% end %>
|
252
269
|
|
253
|
-
Check if **was invoked** with `
|
270
|
+
Check if **was invoked** with `was asked_for` (`was` is an alias of `should`, and `asked_for` is a custom matcher)
|
254
271
|
|
255
272
|
```ruby
|
256
273
|
<% test 'noun invocation', with: :rspec, context: 'mp3 in spec' do %>
|
257
|
-
mp3.
|
274
|
+
mp3.was_not asked_for :info
|
258
275
|
mp3.info
|
259
|
-
mp3.
|
276
|
+
mp3.was asked_for :info
|
260
277
|
<% end %>
|
261
278
|
```
|
262
279
|
|
@@ -266,7 +283,7 @@ Invocation **cardinality** by chaining `times(n)`
|
|
266
283
|
<% test 'noun cardinality', with: :rspec, context: 'mp3 in spec' do %>
|
267
284
|
mp3.info
|
268
285
|
mp3.info
|
269
|
-
mp3.
|
286
|
+
mp3.was asked_for(:info).times(2)
|
270
287
|
<% end %>
|
271
288
|
```
|
272
289
|
|
@@ -275,7 +292,7 @@ Invocation **arguments** by chaining `with(args)`
|
|
275
292
|
```ruby
|
276
293
|
<% test 'noun invocation with args', with: :rspec, context: 'mp3 in spec' do %>
|
277
294
|
mp3.info :title
|
278
|
-
mp3.
|
295
|
+
mp3.was asked_for(:info).with(:title)
|
279
296
|
<% end %>
|
280
297
|
```
|
281
298
|
|
@@ -284,7 +301,7 @@ Supports RSpec's matchers (`no_args`, `hash_including`, etc)
|
|
284
301
|
```ruby
|
285
302
|
<% test 'rspec matchers integration', with: :rspec, context: 'mp3 in spec' do %>
|
286
303
|
mp3.info
|
287
|
-
mp3.
|
304
|
+
mp3.was asked_for(:info).with(no_args)
|
288
305
|
<% end %>
|
289
306
|
```
|
290
307
|
|
@@ -295,8 +312,8 @@ Cardinality of a specific set of args `with(args)` and `times(n)`
|
|
295
312
|
mp3.info :title
|
296
313
|
mp3.info :title
|
297
314
|
mp3.info :artist
|
298
|
-
mp3.
|
299
|
-
mp3.
|
315
|
+
mp3.was asked_for(:info).with(:title).times(2)
|
316
|
+
mp3.was asked_for(:info).with(:artist).times(1)
|
300
317
|
<% end %>
|
301
318
|
```
|
302
319
|
|
@@ -328,13 +345,13 @@ describe 'the example' do
|
|
328
345
|
end
|
329
346
|
<% end %>
|
330
347
|
|
331
|
-
Check if **was invoked** with `
|
348
|
+
Check if **was invoked** with `was told_to`
|
332
349
|
|
333
350
|
```ruby
|
334
351
|
<% test 'have_been_told_to', with: :rspec, context: 'mp3 that plays in spec' do %>
|
335
|
-
mp3.
|
352
|
+
mp3.was_not told_to :play
|
336
353
|
mp3.play
|
337
|
-
mp3.
|
354
|
+
mp3.was told_to :play
|
338
355
|
<% end %>
|
339
356
|
```
|
340
357
|
|
@@ -344,7 +361,7 @@ Also supports the same `with(args)` and `times(n)` that nouns have.
|
|
344
361
|
Initialization
|
345
362
|
--------------
|
346
363
|
|
347
|
-
Query with `
|
364
|
+
Query with `was initialized_with`, which is exactly the same as saying `was told_to(:initialize).with(...)`
|
348
365
|
|
349
366
|
```ruby
|
350
367
|
<% test 'initialization test', with: :rspec, context: 'generic it block' do %>
|
@@ -355,7 +372,7 @@ class MockUser
|
|
355
372
|
end
|
356
373
|
user = MockUser.new 12
|
357
374
|
user.id.should == 12
|
358
|
-
user.
|
375
|
+
user.was initialized_with 12
|
359
376
|
<% end %>
|
360
377
|
```
|
361
378
|
|
@@ -363,7 +380,7 @@ user.should have_been_initialized_with 12
|
|
363
380
|
Predicates
|
364
381
|
----------
|
365
382
|
|
366
|
-
Query qith `
|
383
|
+
Query qith `was asked_if`, all the same chainable methods from above apply.
|
367
384
|
|
368
385
|
```ruby
|
369
386
|
<% test 'initialization test', with: :rspec, context: 'generic it block' do %>
|
@@ -376,16 +393,16 @@ user = MockUser.new
|
|
376
393
|
user.should_not be_admin
|
377
394
|
user.will_have_admin? true
|
378
395
|
user.should be_admin
|
379
|
-
user.
|
396
|
+
user.was asked_if(:admin?).times(2)
|
380
397
|
<% end %>
|
381
398
|
```
|
382
399
|
|
383
400
|
|
384
|
-
class MockUser
|
385
|
-
|
386
401
|
Substitutability
|
387
402
|
----------------
|
388
403
|
|
404
|
+
Facets of substitutability: method existence, argument types, (and soon argument names)
|
405
|
+
|
389
406
|
After you've implemented the real version of your mock (assuming a [top-down](http://vimeo.com/31267109) style of development),
|
390
407
|
how do you prevent your real object from getting out of synch with your mock?
|
391
408
|
|
@@ -393,8 +410,8 @@ Assert that your mock has the **same interface** as your real class.
|
|
393
410
|
This will fail if the mock inherits methods which are not on the real class. It will also fail
|
394
411
|
if the real class has any methods which have not been defined on the mock or inherited by the mock.
|
395
412
|
|
396
|
-
Presently, it will ignore methods defined directly in the mock (as it
|
397
|
-
|
413
|
+
Presently, it will ignore methods defined directly in the mock (as it generally considers them to be helpers).
|
414
|
+
In a future version, you will be able to tell it to treat other methods
|
398
415
|
as part of the API (will fail if they don't match, and maybe record their values).
|
399
416
|
|
400
417
|
```ruby
|
@@ -428,6 +445,13 @@ class UserWithNameAndAddress < UserWithName
|
|
428
445
|
def address()end
|
429
446
|
end
|
430
447
|
MockUser.should_not substitute_for UserWithNameAndAddress
|
448
|
+
|
449
|
+
# signatures don't match
|
450
|
+
class UserWithWrongSignature
|
451
|
+
def initialize()end # no id
|
452
|
+
def id()end
|
453
|
+
end
|
454
|
+
MockUser.should_not substitute_for UserWithWrongSignature
|
431
455
|
<% end %>
|
432
456
|
```
|
433
457
|
|
@@ -489,7 +513,7 @@ describe 'something that creates a user through the service' do
|
|
489
513
|
to_return
|
490
514
|
end
|
491
515
|
|
492
|
-
service.
|
516
|
+
service.was told_to(:create).with { |block|
|
493
517
|
block.call_with({id: new_id}) # this will be given to the block
|
494
518
|
block.returns old_id # provide a return value, or a block that receives the return value (where you can make assertions)
|
495
519
|
block.before { user_id.should == old_id } # assertions about state of the world before the block is called
|
@@ -527,34 +551,3 @@ Special Thanks
|
|
527
551
|
* [Corey Haines](http://coreyhaines.com/) for pairing on substitutability with me
|
528
552
|
* [Enova](http://www.enovafinancial.com/) for giving me time and motivation to work on this during Enova Labs.
|
529
553
|
* [8th Light](http://8thlight.com/) for giving me time to work on this during our weekly Wazas, and the general encouragement and interest
|
530
|
-
|
531
|
-
|
532
|
-
TODO
|
533
|
-
----
|
534
|
-
|
535
|
-
* Remove dependency on all of RSpec and only depend on rspec-core, then have AC tests for the other shit
|
536
|
-
* Add proper failure messages for block invocations
|
537
|
-
* Add a better explanation for motivations
|
538
|
-
* 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)
|
539
|
-
* 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)?)
|
540
|
-
* config: rspec_mocks loaded, whether unprepared blocks should raise or just return nil
|
541
|
-
* extract surrogate/rspec into its own gem
|
542
|
-
* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
|
543
|
-
* Add a last_instance option so you don't have to track it explicitly
|
544
|
-
* make substitutability matcher go either way
|
545
|
-
* make substitutability matcher not care whether either are surrogates
|
546
|
-
* Add support for operators
|
547
|
-
|
548
|
-
|
549
|
-
Future Features
|
550
|
-
---------------
|
551
|
-
|
552
|
-
* figure out how to talk about callbacks like #on_success
|
553
|
-
* have some sort of reinitialization that can hook into setup/teardown steps of test suite
|
554
|
-
* Support arity checking as part of substitutability
|
555
|
-
* 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)
|
556
|
-
* ability to declare normal methods as being part of the API
|
557
|
-
* ability to declare a define that uses the overridden method as the body, but can still act like an api method
|
558
|
-
* assertions for order of invocations & methods
|
559
|
-
* class generator? (supports a top-down style of development for when you write your mocks before you write your implementations)
|
560
|
-
* deal with hard dependency on rspec-mocks
|