muack 0.7.3 → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +3 -5
- data/CHANGES.md +68 -0
- data/Gemfile +4 -0
- data/README.md +734 -287
- data/lib/muack/block.rb +17 -0
- data/lib/muack/definition.rb +3 -2
- data/lib/muack/mock.rb +61 -31
- data/lib/muack/modifier.rb +28 -16
- data/lib/muack/satisfy.rb +5 -5
- data/lib/muack/session.rb +1 -1
- data/lib/muack/test.rb +4 -0
- data/lib/muack/version.rb +1 -1
- data/muack.gemspec +13 -10
- data/task/README.md +54 -0
- data/task/gemgem.rb +9 -11
- data/test/test_any_instance_of.rb +32 -8
- data/test/test_from_readme.rb +40 -0
- data/test/test_mock.rb +37 -26
- data/test/test_modifier.rb +102 -0
- data/test/test_proxy.rb +18 -13
- data/test/test_satisfy.rb +7 -2
- data/test/test_stub.rb +11 -6
- metadata +18 -15
- data/task/.gitignore +0 -1
- data/test/test_readme.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbb0455780531e5a3b3dc2519ba3fa1179f65866
|
4
|
+
data.tar.gz: 91445ca9d049d32bfb110566e4403883158071e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e02fc19cb16d91758b6122e2e704ea81ff298e7766806e1bf08a29be6daebe113635ca943ef0308ccea9cb345d60e46f1699112869b9f3c5a4883cc9c5d5cc3
|
7
|
+
data.tar.gz: 5a4e374c7f7cd81f379817143b7b82939f76a918b6aac6dfc44fd452823fc6a570111a56c033f416630b93106a9b02d81808ba91e342d2a6e27f1107b1d3c66e
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,5 +1,73 @@
|
|
1
1
|
# CHANGES
|
2
2
|
|
3
|
+
## Muack 1.0.0 -- ?
|
4
|
+
|
5
|
+
Improvements:
|
6
|
+
|
7
|
+
* The internal conflicting method names are now a bit more informative
|
8
|
+
and unique thus less likely to have conflicts.
|
9
|
+
|
10
|
+
* Fixed a bug where mock and stub with the same method were defined.
|
11
|
+
Previously, it would raise an undefined method error upon verifying
|
12
|
+
while we're removing injected method. Now it could properly undefine
|
13
|
+
injected methods.
|
14
|
+
|
15
|
+
* Fixed issues mocking private methods. Now it would not only work without
|
16
|
+
a problem, but also preserve the privilege if the original method is a
|
17
|
+
private method. Note that for now, protected methods are treated as
|
18
|
+
public methods though.
|
19
|
+
|
20
|
+
* Fixed a bug where user customized Satisfy could crash if it's located
|
21
|
+
on a top-level. i.e. class names without ::.
|
22
|
+
|
23
|
+
Incompatible changes:
|
24
|
+
|
25
|
+
* Removed proxy method. From now on, if you do not pass a block to a
|
26
|
+
mock, then it would assume it's a proxy. You can think of instead of
|
27
|
+
make an empty block as a default, the original method is the default.
|
28
|
+
That means, previously, mock without a block would always return nil,
|
29
|
+
but now it instead means a proxy.
|
30
|
+
|
31
|
+
* Introduced peek_args method. Sometimes I really need to peek the
|
32
|
+
arguments of a method, or trying to provide a different argument
|
33
|
+
based on the original argument. So peek_args is the way to do it.
|
34
|
+
|
35
|
+
* Introduced peek_return method. By duality, we also introduce something
|
36
|
+
we can peek the return value. Using this along with a custom block
|
37
|
+
doesn't really make sense, but this is actually the previous proxy
|
38
|
+
block. Previously, if a mock is a proxy, then the block doesn't really
|
39
|
+
mean the implementation, but a modification to the original return.
|
40
|
+
That is, the current peek_return. Originally the block is quite
|
41
|
+
inconsistent as the semantics of the block would change depending on
|
42
|
+
it is a proxy or not.
|
43
|
+
|
44
|
+
So to proxy the to_s method and then reverse the result, you write:
|
45
|
+
|
46
|
+
``` ruby
|
47
|
+
str = 'str'
|
48
|
+
Muack::API.mock(str).to_s.peek_return{ |s| s.reverse }
|
49
|
+
p str.to_s # => 'rts'
|
50
|
+
```
|
51
|
+
|
52
|
+
* Removed plain value argument in `returns`. From now on, we should
|
53
|
+
always use the block form. Instead, the argument was changed to be
|
54
|
+
an optional option for specifying if the underlying block should be
|
55
|
+
instance executed or not. By default, the block is lexical scoped.
|
56
|
+
If passing `:instance_exec => true` to `returns`, `peek_args`, and
|
57
|
+
`peek_return`, then the block is instead instance scoped, passing
|
58
|
+
to the instance's `instance_exec`. This way, we would be able to
|
59
|
+
touch the inside of mocked object.
|
60
|
+
|
61
|
+
Without passing `:instance_exec => true`, `to_i` would be called on
|
62
|
+
the top-level object instead. By passing this argument, `to_i` would be
|
63
|
+
called in the string.
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
str = '123'
|
67
|
+
Muack::API.mock(str).int.returns(:instance_exec => true){to_i}
|
68
|
+
p str.int # => 123
|
69
|
+
```
|
70
|
+
|
3
71
|
## Muack 0.7.3 -- 2013-10-01
|
4
72
|
|
5
73
|
* Added `Muack::API.including(element)` for detecting if the underlying
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,10 +10,10 @@ by Lin Jen-Shin ([godfat](http://godfat.org))
|
|
10
10
|
|
11
11
|
## DESCRIPTION:
|
12
12
|
|
13
|
-
Muack --
|
13
|
+
Muack -- A fast, small, yet powerful mocking library.
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
Inspired by [RR][], and it's 32x times faster (750s vs 23s) than RR
|
16
|
+
for running [Rib][] tests.
|
17
17
|
|
18
18
|
[RR]: https://github.com/rr/rr
|
19
19
|
[Rib]: https://github.com/godfat/rib
|
@@ -21,11 +21,11 @@ It's 32x times faster (750s vs 23s) for running [Rib][] tests.
|
|
21
21
|
## WHY?
|
22
22
|
|
23
23
|
Because RR has/had some bugs and it is too complex for me to fix it.
|
24
|
-
Muack is much simpler and thus much faster and
|
24
|
+
Muack is much simpler and thus much faster and much more consistent.
|
25
25
|
|
26
26
|
## REQUIREMENTS:
|
27
27
|
|
28
|
-
* Tested with MRI (official CRuby)
|
28
|
+
* Tested with MRI (official CRuby) 2.0.0, 2.1.0, Rubinius and JRuby.
|
29
29
|
|
30
30
|
## INSTALLATION:
|
31
31
|
|
@@ -33,7 +33,7 @@ Muack is much simpler and thus much faster and less likely to have bugs.
|
|
33
33
|
|
34
34
|
## SYNOPSIS:
|
35
35
|
|
36
|
-
|
36
|
+
Here's a quick example using [Bacon][].
|
37
37
|
|
38
38
|
``` ruby
|
39
39
|
require 'bacon'
|
@@ -55,497 +55,944 @@ end
|
|
55
55
|
|
56
56
|
[Bacon]: https://github.com/chneukirchen/bacon
|
57
57
|
|
58
|
-
###
|
58
|
+
### Overview
|
59
59
|
|
60
|
-
|
61
|
-
Let's see what's the different with code snippets. All codes
|
62
|
-
were extracted from
|
63
|
-
[RR's API document](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md).
|
60
|
+
There are 3 parts in Muack, which are:
|
64
61
|
|
65
|
-
|
62
|
+
* Mocks
|
63
|
+
* Mocks Modifiers
|
64
|
+
* Arguments Verifiers (Satisfy)
|
66
65
|
|
67
|
-
|
66
|
+
Mocks are objects with injected methods which we could observe, and mocks
|
67
|
+
modifiers are telling how we want to observe the mocks, and finally argument
|
68
|
+
verifiers could help us observe the arguments passed to the injected methods.
|
69
|
+
|
70
|
+
Let's explain them one by one.
|
71
|
+
|
72
|
+
### Mocks
|
73
|
+
|
74
|
+
There are also 3 different kinds of mocks in Muack, which are:
|
75
|
+
|
76
|
+
* Mocks
|
77
|
+
* Stubs
|
78
|
+
* Spies
|
79
|
+
|
80
|
+
You could also think of _mocks_ are _stubs_ + _spies_. Here's the equation:
|
81
|
+
|
82
|
+
mock = stub + spy
|
83
|
+
|
84
|
+
Stubs help us inject methods into the objects we want to observe. Spies
|
85
|
+
help us observe the behaviours of the objects. As for mocks, they inject
|
86
|
+
methods and observe the behaviours in realtime. They complain immediately
|
87
|
+
if the behaviours were unexpected. In contrast, if we're not asking spies,
|
88
|
+
stubs won't complain themselves.
|
89
|
+
|
90
|
+
Here's an example using a mock:
|
68
91
|
|
69
92
|
``` ruby
|
70
|
-
|
71
|
-
mock(
|
93
|
+
obj = Object.new
|
94
|
+
mock(obj).name{ 'obj' }
|
95
|
+
p obj.name # 'obj'
|
96
|
+
p Muack.verify # true
|
72
97
|
```
|
73
98
|
|
74
|
-
|
99
|
+
Which is roughly semantically equivalent to using a stub with a spy:
|
75
100
|
|
76
101
|
``` ruby
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
end
|
102
|
+
obj = Object.new
|
103
|
+
stub(obj).name{ 'obj' }
|
104
|
+
p obj.name # 'obj'
|
105
|
+
spy(obj).name
|
106
|
+
p Muack.verify # true
|
84
107
|
```
|
85
108
|
|
86
|
-
|
109
|
+
You might wonder, then why mocks or why stubs with spies? The advantage of
|
110
|
+
using mocks is that, you only need to specify once. I guess this is quite
|
111
|
+
obvious. However, sometimes we don't care if the injected methods are called
|
112
|
+
or not, but sometimes we do care. With stubs and spies, we could always put
|
113
|
+
stubs in the before/setup block, and only when we really care if they are
|
114
|
+
called or not, we put spies to examine.
|
87
115
|
|
88
|
-
|
116
|
+
On the other hand, stubs aren't limited to testing. If we want to monkey
|
117
|
+
patching something, stubs could be useful as we don't care how many times
|
118
|
+
the injected methods are called. Jump to _Muack as a mocky patching library_
|
119
|
+
section for more detail.
|
120
|
+
|
121
|
+
Note that you could also mix mocks and stubs for a given object.
|
122
|
+
Here's an example:
|
89
123
|
|
90
124
|
``` ruby
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
125
|
+
obj = Object.new
|
126
|
+
stub(obj).name{ 'obj' }
|
127
|
+
mock(obj).id { 12345 }
|
128
|
+
p obj.name # 'obj'
|
129
|
+
p obj.id # 12345
|
130
|
+
p Muack.verify # true
|
98
131
|
```
|
99
132
|
|
100
|
-
|
133
|
+
However you should not mix mocks and stubs with the same method, or you
|
134
|
+
might encounter some unexpected result. Jump to _Caveat_ for more detail.
|
101
135
|
|
102
|
-
|
136
|
+
#### Anonymous mode
|
137
|
+
|
138
|
+
Sometimes we just want to stub something without a concrete object in mind.
|
139
|
+
By calling `mock` or `stub` without any argument, we're creating an anonymous
|
140
|
+
mock/stub. This is because the default argument for `mock` and `stub` is just
|
141
|
+
`Object.new`.
|
142
|
+
|
143
|
+
But how do we access the anonymously created object? We'll use the `object`
|
144
|
+
method on the modifier to access it. Here's an example:
|
103
145
|
|
104
146
|
``` ruby
|
105
|
-
|
106
|
-
|
147
|
+
obj = mock.name{ 'obj' }.object
|
148
|
+
p obj.name # 'obj'
|
149
|
+
p Muack.verify # true
|
107
150
|
```
|
108
151
|
|
109
|
-
|
110
|
-
|
111
|
-
Instead of calling `proxy` immediately after calling `mock`, we put
|
112
|
-
`proxy` the last because it's a method from `Muack::Modifier`.
|
152
|
+
This is exactly equivalent to this:
|
113
153
|
|
114
154
|
``` ruby
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
html.should include("John Doe")
|
119
|
-
"Different html"
|
120
|
-
end.proxy
|
155
|
+
mock(obj = Object.new).name{ 'obj' }
|
156
|
+
p obj.name # 'obj'
|
157
|
+
p Muack.verify # true
|
121
158
|
```
|
122
159
|
|
123
|
-
|
124
|
-
|
160
|
+
Also, if we want to mock over multiple methods, we could also take the
|
161
|
+
advantage of block form of `mock` and `stub` method.
|
125
162
|
|
126
163
|
``` ruby
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
164
|
+
obj = mock{ |m|
|
165
|
+
m.name{ 'obj' }
|
166
|
+
m.id { 12345 }
|
167
|
+
}.object
|
168
|
+
p obj.name # 'obj'
|
169
|
+
p obj.id # 12345
|
170
|
+
p Muack.verify # true
|
133
171
|
```
|
134
172
|
|
135
|
-
|
173
|
+
We can't omit the `object` method here because after defining the injected
|
174
|
+
method, we'll get a modifier to describe the properties of the injected
|
175
|
+
method. Jump to _Mocks Modifiers_ for details.
|
176
|
+
|
177
|
+
#### Proxy mode
|
178
|
+
|
179
|
+
There are chances that we don't really want to change the underlying
|
180
|
+
implementation for a given method, but we still want to make sure the
|
181
|
+
named method is called, and that's what we're testing for.
|
182
|
+
|
183
|
+
In those cases, proxy mode would be quite helpful. To turn a mock or stub
|
184
|
+
into proxy mode we simply do not provide any block to the injected method,
|
185
|
+
but just name it. Here's an example:
|
136
186
|
|
137
187
|
``` ruby
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
end.proxy
|
188
|
+
str = 'str'
|
189
|
+
mock(str).reverse
|
190
|
+
p str.reverse # 'rts'
|
191
|
+
p Muack.verify # true
|
143
192
|
```
|
144
193
|
|
145
|
-
|
194
|
+
Note that if reverse was not called exactly once, the mock would complain.
|
195
|
+
We could also use stub + spy to do the same thing as well:
|
146
196
|
|
147
197
|
``` ruby
|
148
|
-
|
149
|
-
stub(
|
150
|
-
|
151
|
-
|
152
|
-
|
198
|
+
str = 'str'
|
199
|
+
stub(str).reverse
|
200
|
+
p str.reverse # 'rts'
|
201
|
+
spy(str).reverse
|
202
|
+
p Muack.verify # true
|
153
203
|
```
|
154
204
|
|
155
|
-
|
205
|
+
You might also want to use `peek_args` and `peek_return` modifier along with
|
206
|
+
proxies in order to slightly tweak the original implementation. Jump to
|
207
|
+
_Muack as a mocky patching library_ section for more detail.
|
208
|
+
|
209
|
+
#### any_instance_of mode
|
210
|
+
|
211
|
+
We only talked about mocking a specific object, but never mentioned what if
|
212
|
+
the objects we want to mock aren't at hand at the time we define mocks?
|
213
|
+
In those cases, instead of trying to mock object creation and return the
|
214
|
+
mock we defined, we might want to simply mock any instance of a particular
|
215
|
+
class, since this would make the process much easier.
|
156
216
|
|
157
|
-
|
158
|
-
|
217
|
+
Here we could use a special "mock" called `any_instance_of`, which takes a
|
218
|
+
class and returns a `Muack::AnyInstanceOf` which represents the instance of
|
219
|
+
the class we just passed. Having this special representation, we could treat
|
220
|
+
it as if a real instance and define regular mocks/stubs on it. It would then
|
221
|
+
applies to any instance of the class we gave.
|
222
|
+
|
223
|
+
Example speaks:
|
159
224
|
|
160
225
|
``` ruby
|
161
|
-
any_instance_of(
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
226
|
+
array = any_instance_of(Array)
|
227
|
+
stub(array).name{ 'array' }
|
228
|
+
p [ ].name # 'array'
|
229
|
+
p [0].name # 'array'
|
230
|
+
p Muack.verify # true
|
167
231
|
```
|
168
232
|
|
169
|
-
|
233
|
+
And as most of the time we don't care about the representation after mocks
|
234
|
+
were defined, we could use the block form:
|
170
235
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
236
|
+
``` ruby
|
237
|
+
any_instance_of(Array) do |array|
|
238
|
+
stub(array).name{ 'array' }
|
239
|
+
stub(array).id { 1234567 }
|
240
|
+
end
|
241
|
+
p [ ].name # 'array'
|
242
|
+
p [0].id # 1234567
|
243
|
+
p Muack.verify # true
|
244
|
+
```
|
176
245
|
|
177
|
-
|
246
|
+
Note that if you need to access the real instance instead of the
|
247
|
+
representation in the injected method, you might want to enable
|
248
|
+
instance_exec mode. Please jump to _instance_exec mode_ section
|
249
|
+
for more detail.
|
178
250
|
|
251
|
+
Here's an quick example:
|
179
252
|
|
180
253
|
``` ruby
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
Muack.verify # This fails, saying `bar` was never called.
|
254
|
+
any_instance_of(Array) do |array|
|
255
|
+
p array.class # Muack::AnyInstanceOf
|
256
|
+
mock(array).name.returns(:instance_exec => true){ inspect }
|
257
|
+
end
|
258
|
+
p [0, 1].name # '[0, 1]'
|
259
|
+
p Muack.verify # true
|
188
260
|
```
|
189
261
|
|
190
|
-
|
191
|
-
|
192
|
-
Block form is also supported. However we don't support `instance_eval` form.
|
193
|
-
There's little point to use instance_eval since it's much more complicated
|
194
|
-
and much slower.
|
262
|
+
Lastly, you could also use `any_instance_of` along with proxy mode,
|
263
|
+
or any other combination you could think of:
|
195
264
|
|
196
265
|
``` ruby
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
expect.system("rake foo:bar") {true}
|
201
|
-
expect.system("rake baz") {true}
|
266
|
+
any_instance_of(Array) do |array|
|
267
|
+
stub(array).name{ 'array' }
|
268
|
+
mock(array).max
|
202
269
|
end
|
270
|
+
p [ ].name # 'array'
|
271
|
+
p [0].max # 0
|
272
|
+
p Muack.verify # true
|
203
273
|
```
|
204
274
|
|
205
|
-
|
275
|
+
Though you should still not mix mocks and stubs with the same method,
|
276
|
+
and as you could tell from the above example, Muack would not complain
|
277
|
+
for every array without calling `max` once. This is because any_instance_of
|
278
|
+
would count on all instances, instead of individual instances. Here
|
279
|
+
we're actually telling Muack that `max` should be called exactly once
|
280
|
+
amongst all instances of array, and it is indeed called exactly once
|
281
|
+
amongst two instances here.
|
282
|
+
|
283
|
+
This might or might not be what we want. But think it twice, if we're
|
284
|
+
mocking any instance of a very basic class in Ruby, testing against
|
285
|
+
individual instances could be too strict since it's used everywhere!
|
286
|
+
|
287
|
+
Please check _Caveat_ section for more details.
|
288
|
+
|
289
|
+
### Mocks Modifiers
|
290
|
+
|
291
|
+
A modifier is something specifying a property of an injected method.
|
292
|
+
By making a mock/stub/spy, it would return a modifier descriptor which
|
293
|
+
we could then specify properties about the injected method.
|
294
|
+
|
295
|
+
Note that we could chain properties for a given modifier descriptor
|
296
|
+
because all public methods for declaring a property would return the
|
297
|
+
modifier descriptor itself. Let's see the specific usages for each
|
298
|
+
properties with concrete examples.
|
299
|
+
|
300
|
+
#### times
|
206
301
|
|
207
|
-
|
208
|
-
|
302
|
+
By using mocks, we are saying that the injected method should be called
|
303
|
+
exactly once. However the injected method might be called more than once,
|
304
|
+
say, twice. We could specify this with `times` modifier:
|
209
305
|
|
210
306
|
``` ruby
|
211
|
-
|
212
|
-
|
307
|
+
obj = Object.new
|
308
|
+
mock(obj).name{ 'obj' }.times(2)
|
309
|
+
p obj.name # 'obj'
|
310
|
+
p obj.name # 'obj'
|
311
|
+
p Muack.verify # true
|
213
312
|
```
|
214
313
|
|
215
|
-
|
314
|
+
This is actually also semantically equivalent to making the mock twice:
|
216
315
|
|
217
316
|
``` ruby
|
218
|
-
|
219
|
-
|
220
|
-
|
317
|
+
obj = Object.new
|
318
|
+
mock(obj).name{ 'obj' }
|
319
|
+
mock(obj).name{ 'obj' }
|
320
|
+
p obj.name # 'obj'
|
321
|
+
p obj.name # 'obj'
|
322
|
+
p Muack.verify # true
|
221
323
|
```
|
222
324
|
|
223
|
-
|
325
|
+
Note that it does not make sense to specify `times` for stubs, because
|
326
|
+
stubs don't care about times. Spies do, though. So this is also
|
327
|
+
semantically equivalent to below:
|
224
328
|
|
225
329
|
``` ruby
|
226
|
-
|
227
|
-
stub(
|
228
|
-
|
229
|
-
|
330
|
+
obj = Object.new
|
331
|
+
stub(obj).name{ 'obj' }
|
332
|
+
p obj.name # 'obj'
|
333
|
+
p obj.name # 'obj'
|
334
|
+
spy(obj).name.times(2)
|
335
|
+
p Muack.verify # true
|
230
336
|
```
|
231
337
|
|
232
|
-
|
233
|
-
|
234
|
-
After defining a mock method, you get a `Muack::Modifier` back.
|
338
|
+
Or without using times for spy:
|
235
339
|
|
236
340
|
``` ruby
|
237
|
-
|
341
|
+
obj = Object.new
|
342
|
+
stub(obj).name{ 'obj' }
|
343
|
+
p obj.name # 'obj'
|
344
|
+
p obj.name # 'obj'
|
345
|
+
spy(obj).name
|
346
|
+
spy(obj).name
|
347
|
+
p Muack.verify # true
|
238
348
|
```
|
239
349
|
|
240
|
-
|
241
|
-
|
350
|
+
The advantage of specifying mocks twice is that we could actually provide
|
351
|
+
different results for each call. You could think of it as a stack. Here's
|
352
|
+
a simple example:
|
242
353
|
|
243
354
|
``` ruby
|
244
|
-
|
355
|
+
obj = Object.new
|
356
|
+
mock(obj).name{ 0 }
|
357
|
+
mock(obj).name{ 1 }
|
358
|
+
mock(obj).name{ 2 }
|
359
|
+
p obj.name # 0
|
360
|
+
p obj.name # 1
|
361
|
+
p obj.name # 2
|
362
|
+
p Muack.verify # true
|
245
363
|
```
|
246
364
|
|
247
|
-
|
248
|
-
you can call `method_missing` directly to mock it. For example, `inspect`
|
249
|
-
is already defined in `Muack::Mock` to avoid crashing with [Bacon][].
|
250
|
-
In this case, you should do this to mock `inspect`:
|
365
|
+
We could also use the block form for convenience:
|
251
366
|
|
252
367
|
``` ruby
|
253
|
-
|
368
|
+
obj = Object.new
|
369
|
+
mock(obj) do |m|
|
370
|
+
m.name{ 0 }
|
371
|
+
m.name{ 1 }
|
372
|
+
m.name{ 2 }
|
373
|
+
end
|
374
|
+
p obj.name # 0
|
375
|
+
p obj.name # 1
|
376
|
+
p obj.name # 2
|
377
|
+
p Muack.verify # true
|
254
378
|
```
|
255
379
|
|
256
|
-
|
257
|
-
|
258
|
-
Again, we embrace one true API to avoid confusion, unless the alternative
|
259
|
-
API really has a great advantage. So we encourage people to use the block to
|
260
|
-
return values. However, sometimes you cannot easily do that for certain
|
261
|
-
methods due to Ruby's syntax. For example, you can't pass a block to
|
262
|
-
a subscript operator `[]`. As a workaround, you can do it with
|
263
|
-
`method_missing`, though it's not very obvious if you don't know
|
264
|
-
what is `method_missing`.
|
380
|
+
Note that this does not apply to stubs because stubs never run out, thus
|
381
|
+
making stubs defined later have no effects at all.
|
265
382
|
|
266
383
|
``` ruby
|
267
|
-
|
268
|
-
|
384
|
+
obj = Object.new
|
385
|
+
stub(obj) do |m|
|
386
|
+
m.name{ 0 }
|
387
|
+
m.name{ 1 }
|
388
|
+
m.name{ 2 }
|
389
|
+
end
|
390
|
+
p obj.name # 0
|
391
|
+
p obj.name # 0
|
392
|
+
p obj.name # 0
|
393
|
+
p Muack.verify # true
|
269
394
|
```
|
270
395
|
|
271
|
-
|
396
|
+
Note that if you do not want a given method be called at all, you could
|
397
|
+
use `times(0)` to enforce this.
|
398
|
+
|
399
|
+
#### with_any_args
|
400
|
+
|
401
|
+
We haven't talked about verifying arguments. With `with_any_args` modifier,
|
402
|
+
we're saying that we don't care about the arguments. If we're not specifying
|
403
|
+
any arguments like above examples, we're saying there's no arguments at all.
|
404
|
+
|
405
|
+
Here we'll show an example for `with_any_args`. If you do want to verify some
|
406
|
+
specific arguments, jump to _Arguments Verifiers_ section.
|
272
407
|
|
273
408
|
``` ruby
|
274
|
-
|
275
|
-
|
409
|
+
obj = Object.new
|
410
|
+
mock(obj).name{ 'obj' }.with_any_args.times(4)
|
411
|
+
p obj.name # 'obj'
|
412
|
+
p obj.name(1) # 'obj'
|
413
|
+
p obj.name(nil) # 'obj'
|
414
|
+
p obj.name(true) # 'obj'
|
415
|
+
p Muack.verify # true
|
276
416
|
```
|
277
417
|
|
278
|
-
|
279
|
-
|
418
|
+
#### returns
|
419
|
+
|
420
|
+
For some methods, we can't really pass a block to specify the implementation.
|
421
|
+
For example, we can't pass a block to `[]`, which is a Ruby syntax limitation.
|
422
|
+
To workaround it, we could use `returns` property:
|
280
423
|
|
281
424
|
``` ruby
|
282
|
-
|
283
|
-
|
425
|
+
obj = Object.new
|
426
|
+
mock(obj)[0].returns{ 0 }
|
427
|
+
p obj[0] # 0
|
428
|
+
p Muack.verify # true
|
284
429
|
```
|
285
430
|
|
286
|
-
|
287
|
-
|
288
|
-
argument for Muack. The example in RR should be changed to this in Muack:
|
431
|
+
This is also useful when we want to put the implementation block in the last
|
432
|
+
instead of the beginning. Here's an example:
|
289
433
|
|
290
434
|
``` ruby
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
435
|
+
obj = Object.new
|
436
|
+
mock(obj).name.times(2).with_any_args.returns{ 'obj' }
|
437
|
+
p obj.name # 'obj'
|
438
|
+
p obj.name # 'obj'
|
439
|
+
p Muack.verify # true
|
296
440
|
```
|
297
441
|
|
298
|
-
|
442
|
+
On the other hand, there's also another advantage of using `returns` than
|
443
|
+
passing the block directly to the injected method. With `returns`, there's
|
444
|
+
an additional option we could use by passing arguments to `returns`. We
|
445
|
+
can't do this in regular injected method definition because those arguments
|
446
|
+
are for verifying the actual arguments. Jump to _Arguments Verifiers_ section
|
447
|
+
for details.
|
299
448
|
|
300
|
-
|
449
|
+
The only option right now is `:instance_exec`.
|
301
450
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
451
|
+
#### instance_exec mode
|
452
|
+
|
453
|
+
By default, the block passed to the injected method is lexically/statically
|
454
|
+
scoped. That means, the scope is bound to the current binding. This is the
|
455
|
+
default because usually we don't need dynamic scopes, and we simply want to
|
456
|
+
return a plain value, and this is much easier to understand, and it is the
|
457
|
+
default for most programming languages, and it would definitely reduce
|
458
|
+
surprises. If we really need to operate on the object, we have it, and
|
459
|
+
we could touch the internal by calling instance_eval on the object.
|
308
460
|
|
309
|
-
|
461
|
+
However, things are a bit different if we're using `any_instance_of`.
|
462
|
+
If we're using `any_instance_of`, then we don't have the instance at
|
463
|
+
hand at the time we're defining the block, but only a `Muack::AnyInstanceOf`
|
464
|
+
instance to represent the instance. There's no way we could really touch
|
465
|
+
the object without `instance_exec` option.
|
310
466
|
|
311
|
-
|
467
|
+
This would also be extremely helpful if we're using Muack as a monkey
|
468
|
+
patching library. We don't have to copy the original codes in order to
|
469
|
+
monkey patching a class, we could simply inject what we really want to
|
470
|
+
fix the internal stuffs in the broken libraries we're using. Jump to
|
471
|
+
_Muack as a mocky patching library_ section for more detail.
|
472
|
+
|
473
|
+
Here's an quick example:
|
312
474
|
|
313
475
|
``` ruby
|
314
|
-
|
315
|
-
|
476
|
+
any_instance_of(Array) do |array|
|
477
|
+
p array.class # Muack::AnyInstanceOf
|
478
|
+
mock(array).name.returns(:instance_exec => true){ inspect }
|
479
|
+
end
|
480
|
+
p [0, 1].name # '[0, 1]'
|
481
|
+
p Muack.verify # true
|
316
482
|
```
|
317
483
|
|
318
|
-
|
484
|
+
Note that this `:instance_exec` option also applies to other modifiers which
|
485
|
+
accepts a block for its implementation, i.e. `peek_args` and `peek_return`.
|
486
|
+
|
487
|
+
#### peek_args
|
319
488
|
|
320
|
-
|
489
|
+
What if we don't really want to change an underlying implementation for a
|
490
|
+
given method, but we just want to slightly change the arguments, or we
|
491
|
+
might just want to take a look at the arguments? Here's an example using
|
492
|
+
`peek_args` to modify the original arguments.
|
493
|
+
|
494
|
+
Note that here we use the proxy mode for the mock, because if we're defining
|
495
|
+
our own behaviour, then we already have full control of the arguments.
|
496
|
+
There's no points to use both. This also applies to `peek_return`.
|
321
497
|
|
322
498
|
``` ruby
|
323
|
-
|
324
|
-
|
325
|
-
|
499
|
+
str = 'ff'
|
500
|
+
mock(str).to_i.with_any_args.peek_args{ |radix| radix * 2 }
|
501
|
+
p str.to_i(8) # 255
|
502
|
+
p Muack.verify # true
|
326
503
|
```
|
327
504
|
|
328
|
-
|
505
|
+
`peek_args` also supports `:instance_exec` mode. Here's an example:
|
329
506
|
|
330
507
|
``` ruby
|
331
|
-
|
332
|
-
stub(
|
333
|
-
|
334
|
-
|
335
|
-
|
508
|
+
any_instance_of(Array) do |array|
|
509
|
+
stub(array).push.with_any_args.
|
510
|
+
peek_args(:instance_exec => true){ |_| size }
|
511
|
+
end
|
512
|
+
a = []
|
513
|
+
p a.push.dup # [0]
|
514
|
+
p a.push.dup # [0, 1]
|
515
|
+
p a.push.dup # [0, 1, 2]
|
516
|
+
p Muack.verify # true
|
336
517
|
```
|
337
518
|
|
338
|
-
|
519
|
+
We could also omit `|_|` if we don't care about the original argument
|
520
|
+
in the above example.
|
339
521
|
|
340
|
-
|
522
|
+
#### peek_return
|
523
|
+
|
524
|
+
What if we don't really want to change an underlying implementation for a
|
525
|
+
given method, but we just want to slightly change the return value, or we
|
526
|
+
might just want to take a look at the return? Here's an example using
|
527
|
+
`peek_return` to modify the original return value.
|
341
528
|
|
342
529
|
``` ruby
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
# ... you get the idea
|
530
|
+
str = 'ff'
|
531
|
+
mock(str).to_i.with_any_args.peek_return{ |int| int * 2 }
|
532
|
+
p str.to_i(16) # 510
|
533
|
+
p Muack.verify # true
|
348
534
|
```
|
349
535
|
|
350
|
-
|
351
|
-
|
352
|
-
Just don't pass any argument :)
|
536
|
+
`peek_return` also supports `:instance_exec` mode. Here's an example:
|
353
537
|
|
354
538
|
``` ruby
|
355
|
-
|
356
|
-
|
357
|
-
|
539
|
+
any_instance_of(Array) do |array|
|
540
|
+
stub(array).push.with_any_args.
|
541
|
+
peek_return(:instance_exec => true){ |_| size }
|
542
|
+
end
|
543
|
+
a = []
|
544
|
+
p a.push(0) # 1
|
545
|
+
p a.push(0) # 2
|
546
|
+
p a.push(0) # 3
|
547
|
+
p a # [0, 0, 0]
|
548
|
+
p Muack.verify # true
|
358
549
|
```
|
359
550
|
|
360
|
-
|
551
|
+
We could also omit `|_|` if we don't care about the original return value
|
552
|
+
in the above example.
|
553
|
+
|
554
|
+
### Arguments Verifiers (Satisfy)
|
555
|
+
|
556
|
+
If we're not passing any arguments to the injected method we define, then
|
557
|
+
basically we're saying that there's no arguments should be passed to the
|
558
|
+
method. If we don't care about the arguments, then we should use
|
559
|
+
`with_any_args` modifier. If we want the *exact* arguments, then we
|
560
|
+
should just pass the arguments, which would be checked with `==` operator.
|
361
561
|
|
362
|
-
|
562
|
+
Here's an example:
|
363
563
|
|
364
564
|
``` ruby
|
365
|
-
|
366
|
-
|
565
|
+
obj = Object.new
|
566
|
+
mock(obj).say('Hi'){ |arg| arg }
|
567
|
+
p obj.say('Hi') # 'Hi'
|
568
|
+
p Muack.verify # true
|
367
569
|
```
|
368
570
|
|
369
|
-
|
571
|
+
This also applies to multiple arguments:
|
370
572
|
|
371
573
|
``` ruby
|
372
|
-
|
373
|
-
mock(
|
374
|
-
|
375
|
-
|
574
|
+
obj = Object.new
|
575
|
+
mock(obj).say('Hello', 'World'){ |*args| args.join(', ') }
|
576
|
+
p obj.say('Hello', 'World') # 'Hello, World'
|
577
|
+
p Muack.verify # true
|
376
578
|
```
|
377
579
|
|
378
|
-
|
580
|
+
We could also retrieve the block argument:
|
379
581
|
|
380
|
-
|
582
|
+
``` ruby
|
583
|
+
obj = Object.new
|
584
|
+
mock(obj).say{ |&block| block.call('Hi') }
|
585
|
+
obj.say{ |msg| p msg } # 'Hi'
|
586
|
+
p Muack.verify # true
|
587
|
+
```
|
588
|
+
|
589
|
+
Moreover, we could also have stubs on the same method for different
|
590
|
+
arguments. We could think of this as a sort of pattern matching, and Muack
|
591
|
+
would try to find the best matched stub for us.
|
381
592
|
|
382
593
|
``` ruby
|
383
|
-
|
384
|
-
|
385
|
-
|
594
|
+
obj = Object.new
|
595
|
+
stub(obj).find(0){ 0 }
|
596
|
+
stub(obj).find(1){ 1 }
|
597
|
+
p obj.find(1) # 1
|
598
|
+
p obj.find(0) # 0
|
599
|
+
p Muack.verify # true
|
386
600
|
```
|
387
601
|
|
388
|
-
|
602
|
+
If `obj.find(2)` is called and Muack cannot find a matched stub, it would
|
603
|
+
raise a `Muack::Unexpected` and list the candidates for us.
|
604
|
+
|
605
|
+
However, What if we don't want to be so exact? Then we should use verifiers.
|
606
|
+
We'll introduce each of them in next section. Note that verifiers
|
607
|
+
are not recursive though. If you need complex argument verification,
|
608
|
+
you'll need to use `satisfy` verifier which you could give an arbitrary
|
609
|
+
block to verify anything.
|
389
610
|
|
390
|
-
|
611
|
+
#### is_a
|
612
|
+
|
613
|
+
`is_a` would check if the argument is a kind of the given class.
|
614
|
+
Actually, it's calling `kind_of?` underneath.
|
391
615
|
|
392
616
|
``` ruby
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
object.foo # fails
|
617
|
+
obj = Object.new
|
618
|
+
mock(obj).say(is_a(String)){ |arg| arg }
|
619
|
+
p obj.say('something') # 'something'
|
620
|
+
p Muack.verify # true
|
398
621
|
```
|
399
622
|
|
400
|
-
|
623
|
+
#### anything
|
624
|
+
|
625
|
+
`anything` is a wildcard argument verifier. It matches anything.
|
626
|
+
Although this actually verifies nothing, we could still think of
|
627
|
+
this as an arity verifier. Since one anything is not two anythings.
|
401
628
|
|
402
629
|
``` ruby
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
630
|
+
obj = Object.new
|
631
|
+
mock(obj).say(anything){ |arg| arg }.times(2)
|
632
|
+
p obj.say(0) # 0
|
633
|
+
p obj.say(true) # true
|
634
|
+
p Muack.verify # true
|
408
635
|
```
|
409
636
|
|
410
|
-
####
|
637
|
+
#### match
|
411
638
|
|
412
|
-
|
639
|
+
`match` would check the argument with `match` method. Usually this is
|
640
|
+
used with regular expression, but anything which responds to `match`
|
641
|
+
should work.
|
413
642
|
|
414
643
|
``` ruby
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
raise "BOOM" if times <= 3
|
644
|
+
obj = Object.new
|
645
|
+
mock(obj).say(match(/\w+/)){ |arg| arg }
|
646
|
+
p obj.say('Hi') # 'Hi'
|
647
|
+
p Muack.verify # true
|
420
648
|
```
|
421
649
|
|
422
|
-
|
650
|
+
Note that please don't pass the regular expression directly without
|
651
|
+
wrapping it with a match verifier, or how do we distinguish if we
|
652
|
+
really want to make sure the argument is exactly the regular expression?
|
423
653
|
|
654
|
+
#### hash_including
|
424
655
|
|
425
|
-
|
656
|
+
`hash_including` would check if the given hash is the actual
|
657
|
+
argument's subset.
|
426
658
|
|
427
659
|
``` ruby
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
object.foo
|
433
|
-
object.foo
|
660
|
+
obj = Object.new
|
661
|
+
mock(obj).say(hash_including(:a => 0)){ |arg| arg }
|
662
|
+
p obj.say(:a => 0, :b => 1) # {:a => 0, :b => 1}
|
663
|
+
p Muack.verify # true
|
434
664
|
```
|
435
665
|
|
436
|
-
####
|
666
|
+
#### including
|
437
667
|
|
438
|
-
|
668
|
+
`including` would check if the actual argument includes the given value
|
669
|
+
via `include?` method.
|
439
670
|
|
440
671
|
``` ruby
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
672
|
+
obj = Object.new
|
673
|
+
mock(obj).say(including(0)){ |arg| arg }
|
674
|
+
p obj.say([0,1]) # [0,1]
|
675
|
+
p Muack.verify # true
|
445
676
|
```
|
446
677
|
|
447
|
-
####
|
678
|
+
#### within
|
448
679
|
|
449
|
-
`
|
680
|
+
`within` is the reverse version of `including`, verifying if the actual
|
681
|
+
argument is included in the given value.
|
450
682
|
|
451
683
|
``` ruby
|
452
|
-
|
453
|
-
|
684
|
+
obj = Object.new
|
685
|
+
mock(obj).say(within([0, 1])){ |arg| arg }
|
686
|
+
p obj.say(0) # 0
|
687
|
+
p Muack.verify # true
|
454
688
|
```
|
455
689
|
|
456
|
-
|
690
|
+
#### respond_to
|
691
|
+
|
692
|
+
`respond_to` would check if the actual argument would be responding to
|
693
|
+
the given message, checked via `respond_to?`, also known as duck typing.
|
457
694
|
|
458
695
|
``` ruby
|
459
|
-
|
460
|
-
|
696
|
+
obj = Object.new
|
697
|
+
mock(obj).say(respond_to(:size)){ |arg| arg }
|
698
|
+
p obj.say([]) # []
|
699
|
+
p Muack.verify # true
|
461
700
|
```
|
462
701
|
|
463
|
-
|
702
|
+
Note that you could give multiple messages to `respond_to`.
|
464
703
|
|
465
704
|
``` ruby
|
466
|
-
|
467
|
-
|
705
|
+
obj = Object.new
|
706
|
+
mock(obj).say(respond_to(:size, :reverse)){ |arg| arg }
|
707
|
+
p obj.say([]) # []
|
708
|
+
p Muack.verify # true
|
468
709
|
```
|
469
710
|
|
470
|
-
|
711
|
+
#### satisfy
|
712
|
+
|
713
|
+
`satisfy` accepts a block to let you do arbitrary verification.
|
714
|
+
nil and false are considered false, otherwise true, just like in
|
715
|
+
regular if expression.
|
471
716
|
|
472
717
|
``` ruby
|
473
|
-
|
474
|
-
|
718
|
+
obj = Object.new
|
719
|
+
mock(obj).say(satisfy{ |arg| arg % 2 == 0 }){ |arg| arg }
|
720
|
+
p obj.say(0) # 0
|
721
|
+
p Muack.verify # true
|
475
722
|
```
|
476
723
|
|
477
|
-
|
724
|
+
#### Disjunction (|)
|
725
|
+
|
726
|
+
If what we want is the actual argument be within either `0..1` or `3..4`?
|
727
|
+
We don't really have to use `satisfy` to build custom verifier, we could
|
728
|
+
compose verifiers with disjunction operator (|).
|
478
729
|
|
479
730
|
``` ruby
|
480
|
-
|
481
|
-
arg
|
482
|
-
|
483
|
-
|
484
|
-
|
731
|
+
obj = Object.new
|
732
|
+
mock(obj).say(within(0..1) | within(3..4)){ |arg| arg }.times(2)
|
733
|
+
p obj.say(0) # 0
|
734
|
+
p obj.say(4) # 4
|
735
|
+
p Muack.verify # true
|
485
736
|
```
|
486
737
|
|
487
|
-
|
488
|
-
Though there's not much point here. Just want to demonstrate.
|
738
|
+
Or boolean, you might say:
|
489
739
|
|
490
740
|
``` ruby
|
491
|
-
|
492
|
-
arg
|
493
|
-
|
494
|
-
|
495
|
-
|
741
|
+
obj = Object.new
|
742
|
+
mock(obj).say(is_a(TrueClass) | is_a(FalseClass)){ |arg| arg }.times(2)
|
743
|
+
p obj.say(true) # true
|
744
|
+
p obj.say(false) # false
|
745
|
+
p Muack.verify # true
|
496
746
|
```
|
497
747
|
|
498
|
-
|
499
|
-
|
748
|
+
#### Conjunction (&)
|
749
|
+
|
750
|
+
If what we want is the actual argument not only a kind of something,
|
751
|
+
but also responds to something. For example, an Enumerable requires the
|
752
|
+
class implements each method. We could use conjunction for this.
|
500
753
|
|
501
754
|
``` ruby
|
502
|
-
|
503
|
-
|
755
|
+
obj = Object.new
|
756
|
+
mock(obj).say(is_a(Enumerable) & respond_to(:each)){}.times(3)
|
757
|
+
p obj.say( [] ) # nil
|
758
|
+
p obj.say( {} ) # nil
|
759
|
+
p obj.say(0..1) # nil
|
760
|
+
p Muack.verify # true
|
504
761
|
```
|
505
762
|
|
506
|
-
|
763
|
+
### Caveat
|
764
|
+
|
765
|
+
#### Mixing mocks and stubs
|
766
|
+
|
767
|
+
We could and probably would also want to mix mocks and stubs, for example,
|
768
|
+
we might be concerned about some methods for a given object, but not the
|
769
|
+
other methods.
|
507
770
|
|
508
771
|
``` ruby
|
509
|
-
|
510
|
-
|
772
|
+
obj = Object.new
|
773
|
+
stub(obj).name{ 'obj' }
|
774
|
+
mock(obj).id { 12345 }
|
775
|
+
p obj.name # 'obj'
|
776
|
+
p obj.name # 'obj'
|
777
|
+
p obj.id # 12345
|
778
|
+
p Muack.verify # true
|
511
779
|
```
|
512
780
|
|
513
|
-
|
781
|
+
However, it might act unexpectedly if we mock and stub on the same object
|
782
|
+
for the same method. It would somehow act like the latter would always win!
|
783
|
+
So if we define mock later for the same method, previously defined stub
|
784
|
+
would never be called. On the other hand, if we define stub later for the
|
785
|
+
same method, previously defined mock would always complain because it would
|
786
|
+
never be called, either!
|
787
|
+
|
788
|
+
This does not mean previously defined mocks or stubs get overwritten, because
|
789
|
+
it would still take effect. It's just that there's no way they could get
|
790
|
+
called. So this is mostly not desired.
|
791
|
+
|
792
|
+
The ideal solution to this would be raising an error immediately, or really
|
793
|
+
make it could be overwritten. However I didn't find a good way to handle this
|
794
|
+
without rewriting the internal details. So I'll just leave it as it is,
|
795
|
+
and hope no one would ever try to do this.
|
796
|
+
|
797
|
+
#### any_instance_of shares all calls for a given class
|
798
|
+
|
799
|
+
We might assume that mocks with any_instance_of would work exactly the same
|
800
|
+
as regular mocks, but this is actually not the case. Regular mocks count
|
801
|
+
on every individual instance, but all instances share the same count for
|
802
|
+
any_instance_of.
|
803
|
+
|
804
|
+
With one instance:
|
514
805
|
|
515
806
|
``` ruby
|
516
|
-
mock(
|
517
|
-
|
807
|
+
any_instance_of(Array){ |array| mock(array).f{true}.times(2) }
|
808
|
+
a = []
|
809
|
+
p a.f # true
|
810
|
+
p a.f # true
|
811
|
+
p Muack.verify # true
|
518
812
|
```
|
519
813
|
|
520
|
-
|
814
|
+
With two instances:
|
521
815
|
|
522
816
|
``` ruby
|
523
|
-
|
524
|
-
|
817
|
+
any_instance_of(Array){ |array| mock(array).f{true}.times(2) }
|
818
|
+
p [].f # true
|
819
|
+
p [].f # true
|
820
|
+
p Muack.verify # true
|
821
|
+
```
|
822
|
+
|
823
|
+
So remember to count on all instances, but not individual ones.
|
824
|
+
|
825
|
+
### Extra Topics
|
826
|
+
|
827
|
+
#### Muack as a mocky patching library
|
828
|
+
|
829
|
+
Consider you're using a broken library and you need an immediate fix without
|
830
|
+
waiting for upstream to merge your patch, and release a new version.
|
831
|
+
|
832
|
+
You could fix it more elegantly by subclassing the original class, or try to
|
833
|
+
include or extend a module to make the original class work correctly. But
|
834
|
+
sometimes we just cannot do this because of the implementation. They might
|
835
|
+
not be extensible at all. Consider if there's a method contains 1,000
|
836
|
+
lines... There's no way to change it in the middle of the method other than
|
837
|
+
touching the lines directly, unless we have some line based AOP tools...
|
838
|
+
which is not really practical.
|
839
|
+
|
840
|
+
In this case, we could fork it and maintain everything by ourselves, and
|
841
|
+
merge from upstream occasionally. However we might only want to do this as
|
842
|
+
the last resort since this could cost a lot.
|
843
|
+
|
844
|
+
Alternatively, we can copy the original code, and put it somewhere, and
|
845
|
+
load it after the original code was loaded, so we have the patched and
|
846
|
+
correct code running. This is also called monkey patching, patching like a
|
847
|
+
monkey. Generally this is a bad idea, but sometimes we can only do this to
|
848
|
+
workaround some broken libraries. For example, some libraries might not be
|
849
|
+
maintained, or the authors refused to fix this due to other reasonable or
|
850
|
+
unreasonable reason.
|
851
|
+
|
852
|
+
The most notable drawback of monkey patching is that, we're copying a lot of
|
853
|
+
codes which could be changed upstream, and we might not be aware of that,
|
854
|
+
and update our monkey patch accordingly. This could cause some incompatible
|
855
|
+
issues.
|
856
|
+
|
857
|
+
That means, the fewer copied codes, the better. Muack could actually help
|
858
|
+
in this case. I called this mocky patching. The advantage of using this
|
859
|
+
technique is that, we have `peek_args` and `peek_return` which we could
|
860
|
+
modify the arguments or return values in runtime, without changing any
|
861
|
+
implementation of a particular method.
|
862
|
+
|
863
|
+
Here's a real world example with rails_admin. The problem in rails_admin is
|
864
|
+
that, it assumes every associated records should have already been saved,
|
865
|
+
thus having an id, and there's also a particular show page for it.
|
866
|
+
|
867
|
+
However, in our application, we could have associated records not yet saved
|
868
|
+
in the database. rails_admin would try to retrieve routes for those unsaved
|
869
|
+
records, and rails would raise RoutingError because rails_admin is passing
|
870
|
+
no id for a show path.
|
871
|
+
|
872
|
+
The idea of this fix is simple. Just don't try to get the show page for
|
873
|
+
records which are not yet saved, i.e. records without an id. However this
|
874
|
+
is actually extremely hard to fix in rails_admin without monkey patching!
|
875
|
+
|
876
|
+
I'll skip all those details and my rants. In the end, I fixed this by
|
877
|
+
trying to peek the arguments for a particular method, and if and only if
|
878
|
+
the passed records are not yet saved in the database, we fake the arguments.
|
879
|
+
Otherwise, we just bypass and fallback to the original implementation.
|
880
|
+
|
881
|
+
Here's the code:
|
882
|
+
|
883
|
+
``` ruby
|
884
|
+
Muack::API.stub(RailsAdmin::Config::Actions).find.with_any_args.
|
885
|
+
peek_args do |*args|
|
886
|
+
custom_key, bindings = args
|
887
|
+
if bindings && bindings[:object] && bindings[:object].id.nil?
|
888
|
+
[nil, {}] # There's no show page for unsaved records
|
889
|
+
else
|
890
|
+
args # Bypass arguments
|
891
|
+
end
|
892
|
+
end
|
893
|
+
```
|
894
|
+
|
895
|
+
If we don't do mocky patching but monkey patching, we'll end up with
|
896
|
+
copying the entire method for RailsAdmin::Config::Actions.find, which then,
|
897
|
+
we'll be responsible for updating this method if some of the original
|
898
|
+
implementation changed.
|
899
|
+
|
900
|
+
Note that in mocky patching, we should always use stub and never call
|
901
|
+
`Muack.verify` or `Muack.reset`, or that would defeat the purpose of
|
902
|
+
mocky patching.
|
903
|
+
|
904
|
+
#### Muack as a development runtime static typing system
|
905
|
+
|
906
|
+
Ever consider a static type system in Ruby? You could actually see a lot of
|
907
|
+
asserts inserted in the beginning of some methods in some libraries. For
|
908
|
+
example, there are `assert_valid_key_size`, `assert_kind_of`, etc, in
|
909
|
+
[dm-core][], and `assert_valid_keys`, `assert_valid_transaction_action`,
|
910
|
+
and various random asserts in activerecord.
|
911
|
+
|
912
|
+
You could find them by searching against `raise ArgumentError` because
|
913
|
+
rails is much less consistent and sometimes it's hard to find a pattern in
|
914
|
+
rails. But you get the idea, those `ArgumentError` would much help us debug
|
915
|
+
our code from misusing the API, and that's exactly the point of type system,
|
916
|
+
or more specifically, static type system.
|
917
|
+
|
918
|
+
We could also use some static analysis tools to do something like this, for
|
919
|
+
example, there's [ruby-lint][]. However, as you might already know, since
|
920
|
+
Ruby is so dynamic, static analysis tools cannot really do a great job if
|
921
|
+
our code is quite dynamic. Of course we could write it more statically,
|
922
|
+
and treat our static analysis tools better, but that might not be the spirit
|
923
|
+
of Ruby somehow.
|
924
|
+
|
925
|
+
Alternatively, it would be great to do this static type checking
|
926
|
+
dynamically... I mean, in the runtime rather than compile time. This
|
927
|
+
means it would be much more accurate, just like those asserts in the
|
928
|
+
above examples.
|
929
|
+
|
930
|
+
However, if we're doing those checks in a hot path, for example, right
|
931
|
+
inside a loop looping over a million times, this would definitely slow
|
932
|
+
things down if we're checking them in the runtime. Even if we put `$DEBUG`
|
933
|
+
guards around those check, we're still suffering from checking the flag.
|
934
|
+
|
935
|
+
It would be great if we could actually just remove those checks in
|
936
|
+
production, while turn it on when we're developing or debugging.
|
937
|
+
Muack could actually fulfill this desire, as it could inject codes
|
938
|
+
externally and seamlessly, and we could remove them anytime when we
|
939
|
+
call `Muack.reset`, or, simply don't do any stubs in production config.
|
940
|
+
|
941
|
+
Consider we have two classes:
|
942
|
+
|
943
|
+
``` ruby
|
944
|
+
Food = Class.new
|
945
|
+
User = Class.new{ attr_accessor :food }
|
946
|
+
```
|
947
|
+
|
948
|
+
And we could make sure User#food is always a kind of `Food` by putting this
|
949
|
+
into a development config or so:
|
950
|
+
|
951
|
+
``` ruby
|
952
|
+
Muack::API.module_eval do
|
953
|
+
any_instance_of(User) do |user|
|
954
|
+
stub(user).food = is_a(Food)
|
955
|
+
end
|
956
|
+
end
|
525
957
|
```
|
526
958
|
|
527
|
-
|
959
|
+
And then if we're trying to set a food other than a `Food`...
|
960
|
+
|
961
|
+
``` ruby
|
962
|
+
u, f = User.new, Food.new
|
963
|
+
u.food = f # ok
|
964
|
+
u.food = 1 # raise Muack::Unexpected
|
965
|
+
```
|
528
966
|
|
529
|
-
|
530
|
-
|
967
|
+
This could go wild and we could customize our own domain specific argument
|
968
|
+
verifiers. For example, we could do this to check if the food is frozen:
|
531
969
|
|
532
970
|
``` ruby
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
971
|
+
Food = Class.new
|
972
|
+
User = Class.new{ attr_accessor :food }
|
973
|
+
|
974
|
+
FoodFrozen = Class.new(Muack::Satisfy) do
|
975
|
+
def initialize
|
976
|
+
super lambda{ |actual_arg| actual_arg.frozen? }
|
537
977
|
end
|
538
978
|
end
|
539
979
|
|
540
|
-
|
541
|
-
|
542
|
-
|
980
|
+
Muack::API.module_eval do
|
981
|
+
any_instance_of(User) do |user|
|
982
|
+
stub(user).food = FoodFrozen.new
|
543
983
|
end
|
544
984
|
end
|
985
|
+
|
986
|
+
u = User.new
|
987
|
+
p u.food = Food.new.freeze # ok
|
988
|
+
p u.food = Food.new # raise Muack::Unexpected
|
545
989
|
```
|
546
990
|
|
547
|
-
|
548
|
-
|
991
|
+
Please check _Arguments Verifiers (Satisfy)_ section for more argument
|
992
|
+
verifiers details.
|
993
|
+
|
994
|
+
[dm-core]: https://github.com/datamapper/dm-core
|
995
|
+
[ruby-lint]: https://github.com/YorickPeterse/ruby-lint
|
549
996
|
|
550
997
|
## USERS:
|
551
998
|
|
@@ -561,7 +1008,7 @@ end
|
|
561
1008
|
|
562
1009
|
Apache License 2.0
|
563
1010
|
|
564
|
-
Copyright (c) 2013, Lin Jen-Shin (godfat)
|
1011
|
+
Copyright (c) 2013~2014, Lin Jen-Shin (godfat)
|
565
1012
|
|
566
1013
|
Licensed under the Apache License, Version 2.0 (the "License");
|
567
1014
|
you may not use this file except in compliance with the License.
|