muack 0.7.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f70136437354cf553340e7a4502e6fc4056802e
4
- data.tar.gz: 4f73fe1b681f0e6b4b837f89e72865a008f721af
3
+ metadata.gz: fbb0455780531e5a3b3dc2519ba3fa1179f65866
4
+ data.tar.gz: 91445ca9d049d32bfb110566e4403883158071e5
5
5
  SHA512:
6
- metadata.gz: 832585b3ec72cf71fdd5fd1a607034ddd999e509d03e8239037fc2bb7a3a9d1637996a09ec2e1d2d45ace2539d3b8d4ee5c6877df02ed3a0113e3e2f125d8869
7
- data.tar.gz: c9c64a40ec5c10d2cbdf30d17c3952b857915160f7599f8a2258db88ad53d668d75c5f493399d0f315c811d9164ab41267498b329fa2bf8202f2f0dcfe93893f
6
+ metadata.gz: 9e02fc19cb16d91758b6122e2e704ea81ff298e7766806e1bf08a29be6daebe113635ca943ef0308ccea9cb345d60e46f1699112869b9f3c5a4883cc9c5d5cc3
7
+ data.tar.gz: 5a4e374c7f7cd81f379817143b7b82939f76a918b6aac6dfc44fd452823fc6a570111a56c033f416630b93106a9b02d81808ba91e342d2a6e27f1107b1d3c66e
data/.gitignore CHANGED
@@ -1,2 +1 @@
1
1
  /pkg/
2
- *.rbc
data/.travis.yml CHANGED
@@ -1,11 +1,9 @@
1
1
  before_install: 'git submodule update --init'
2
2
  script: 'ruby -r bundler/setup -S rake test'
3
3
 
4
- env:
5
- - 'RBXOPT=-X19'
6
-
7
4
  rvm:
8
5
  - 1.9.3
9
6
  - 2.0.0
10
- - rbx-head
11
- - jruby-head
7
+ - 2.1.0
8
+ - rbx
9
+ - jruby
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
@@ -5,3 +5,7 @@ gemspec
5
5
 
6
6
  gem 'rake'
7
7
  gem 'bacon'
8
+
9
+ platform :rbx do
10
+ gem 'rubysl-singleton' # used in rake
11
+ end
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 -- Yet another mocking library.
13
+ Muack -- A fast, small, yet powerful mocking library.
14
14
 
15
- Basically it's an [RR][] clone, but much faster under heavy use.
16
- It's 32x times faster (750s vs 23s) for running [Rib][] tests.
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 less likely to have bugs.
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) 1.9.3, 2.0.0, Rubinius and JRuby.
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
- Basically it's an [RR][] clone. Let's see a [Bacon][] example.
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
- ### Coming from RR?
58
+ ### Overview
59
59
 
60
- Basically since it's an RR clone, the APIs are much the same.
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
- #### [mock](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#mock)
62
+ * Mocks
63
+ * Mocks Modifiers
64
+ * Arguments Verifiers (Satisfy)
66
65
 
67
- `mock` is the same as RR.
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
- view = controller.template
71
- mock(view).render(:partial => "user_info") {"Information"}
93
+ obj = Object.new
94
+ mock(obj).name{ 'obj' }
95
+ p obj.name # 'obj'
96
+ p Muack.verify # true
72
97
  ```
73
98
 
74
- There's no `twice` modifier in Muack, use `times(2)` instead.
99
+ Which is roughly semantically equivalent to using a stub with a spy:
75
100
 
76
101
  ``` ruby
77
- mock(view).render.with_any_args.times(2) do |*args|
78
- if args.first == {:partial => "user_info"}
79
- "User Info"
80
- else
81
- "Stuff in the view #{args.inspect}"
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
- #### [stub](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#stub)
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
- `stub` is the same as RR.
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
- jane = User.new
92
- bob = User.new
93
- stub(User).find('42') {jane}
94
- stub(User).find('99') {bob}
95
- stub(User).find do |id|
96
- raise "Unexpected id #{id.inspect} passed to me"
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
- #### [times(0)](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#dont_allow-aliased-to-do_not_allow-dont_call-and-do_not_call)
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
- There's no dont_allow method in Muack, use `times(0)` instead.
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
- User.find('42').times(0)
106
- User.find('42') # raises a Muack::Unexpected
147
+ obj = mock.name{ 'obj' }.object
148
+ p obj.name # 'obj'
149
+ p Muack.verify # true
107
150
  ```
108
151
 
109
- #### [proxy](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#mockproxy)
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
- view = controller.template
116
- mock(view).render(:partial => "right_navigation").proxy
117
- mock(view).render(:partial => "user_info") do |html|
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
- If you feel it is weird to put proxy the last, you can also use
124
- `returns` modifier to put the block last as this:
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
- view = controller.template
128
- mock(view).render(:partial => "right_navigation").proxy
129
- mock(view).render(:partial => "user_info").proxy.returns do |html|
130
- html.should include("John Doe")
131
- "Different html"
132
- end
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
- The same goes to `stub`.
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
- view = controller.template
139
- stub(view).render(:partial => "user_info") do |html|
140
- html.should include("Joe Smith")
141
- html
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
- Or use `returns`:
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
- view = controller.template
149
- stub(view).render(:partial => "user_info").proxy.returns do |html|
150
- html.should include("Joe Smith")
151
- html
152
- end
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
- #### [any_instance_of](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#any_instance_of)
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
- Only this form of `any_instance_of` is supported. On the other hand,
158
- any of the above is supported as well, not only stub.
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(User) do |u|
162
- stub(u).valid? { false }
163
- mock(u).errors { [] }
164
- mock(u).save.proxy
165
- stub(u).reload.proxy
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
- #### [Spies](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#spies)
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
- We don't try to provide different methods for different testing framework,
172
- so that we don't have to create so many testing framework adapters, and
173
- try to be smart to find the correct adapter. There are simply too many
174
- testing frameworks out there. Ruby's built-in test/unit and minitest have
175
- a lot of different versions, so does rspec.
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
- Here we just try to do it the Muack's way:
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
- subject = Object.new
182
- stub(subject).foo(1)
183
- subject.foo(1)
184
-
185
- spy(subject).foo(1)
186
- spy(subject).bar # This doesn't verify immediately.
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
- #### [Block form](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#block-syntax)
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
- script = MyScript.new
198
- mock(script) do |expect|
199
- expect.system("cd #{RAILS_ENV}") {true}
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
- #### [Nested mocks](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#double-graphs)
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
- The shortest API (which might be a bit tricky) is not supported,
208
- but we do support:
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
- stub(object).foo { stub.bar{ :baz }.object }
212
- object.foo.bar #=> :baz
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
- And of course the verbose way:
314
+ This is actually also semantically equivalent to making the mock twice:
216
315
 
217
316
  ``` ruby
218
- bar = stub.bar{ :baz }.object
219
- stub(object).foo { bar }
220
- object.foo.bar #=> :baz
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
- Or even more verbose, of course:
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
- bar = Object.new
227
- stub(bar).bar{ :baz }
228
- stub(object).foo { bar }
229
- object.foo.bar #=> :baz
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
- #### [Modifier](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#modifying-doubles)
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
- stub(object).foo #=> Muack::Modifier
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
- However, you cannot flip around methods like RR. Whenever you define a
241
- mock/stub method, you must provide the block immediately.
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
- mock(object).foo{ 'bar' }.times(2)
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
- If unfortunately, the method name you want to mock is already defined,
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
- mock(object).method_missing(:inspect){ 'bar' }.times(2)
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
- #### [Stubbing method implementation / return value](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#stubbing-method-implementation--return-value)
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
- stub(object).method_missing(:[], is_a(Fixnum)){ |a| a+1 }
268
- object[1] #=> 2
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
- Instead you can do this with `returns`:
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
- stub(object)[is_a(Fixnum)].returns{ |a| a + 1 }
275
- object[1] #=> 2
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
- You can also pass a value directly to `returns` if you only want to return
279
- a simple value.
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
- stub(object)[is_a(Fixnum)].returns(2)
283
- object[1] #=> 2
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
- On the other hand, since Muack is more strict than RR. Passing no arguments
287
- means you really don't want any argument. Here we need to specify the
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
- stub(object).foo(is_a(Fixnum), anything){ |age, count, &block|
292
- raise 'hell' if age < 16
293
- ret = block.call count
294
- blue? ? ret : 'whatever'
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
- #### [Stubbing method implementation based on argument expectation](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#stubbing-method-implementation-based-on-argument-expectation)
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
- Here is exactly the same as RR.
449
+ The only option right now is `:instance_exec`.
301
450
 
302
- ``` ruby
303
- stub(object).foo { 'bar' }
304
- stub(object).foo(1, 2) { 'baz' }
305
- object.foo #=> 'bar'
306
- object.foo(1, 2) #=> 'baz'
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
- #### [Stubbing method to yield given block](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#stubbing-method-to-yield-given-block)
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
- Always use the block to pass whatever back.
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
- stub(object).foo{ |&block| block.call(1, 2, 3) }
315
- object.foo {|*args| args } # [1, 2, 3]
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
- #### [Expecting method to be called with exact argument list](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-be-called-with-exact-argument-list)
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
- Muack is strict, you always have to specify the argument list.
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
- mock(object).foo(1, 2)
324
- object.foo(1, 2) # ok
325
- object.foo(3) # fails
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
- Passing no arguments really means passing no arguments.
505
+ `peek_args` also supports `:instance_exec` mode. Here's an example:
329
506
 
330
507
  ``` ruby
331
- stub(object).foo
332
- stub(object).foo(1, 2)
333
- object.foo(1, 2) # ok
334
- object.foo # ok
335
- object.foo(3) # fails
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
- #### [Expecting method to be called with any arguments](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-be-called-with-any-arguments)
519
+ We could also omit `|_|` if we don't care about the original argument
520
+ in the above example.
339
521
 
340
- Muack also provides `with_any_args` if we don't really care.
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
- stub(object).foo.with_any_args
344
- object.foo # ok
345
- object.foo(1) # also ok
346
- object.foo(1, 2) # also ok
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
- #### [Expecting method to be called with no arguments](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-be-called-with-no-arguments)
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
- stub(object).foo
356
- object.foo # ok
357
- object.foo(1) # fails
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
- #### [Expecting method to never be called](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-never-be-called)
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
- Simply use `times(0)`.
562
+ Here's an example:
363
563
 
364
564
  ``` ruby
365
- mock(object).foo.times(0)
366
- object.foo # fails
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
- Multiple mock with different argument set is fine, too.
571
+ This also applies to multiple arguments:
370
572
 
371
573
  ``` ruby
372
- mock(object).foo(1, 2).times(0)
373
- mock(object).foo(3, 4)
374
- object.foo(3, 4) # ok
375
- object.foo(1, 2) # fails
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
- #### [Expecting method to be called only once](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-be-called-only-once)
580
+ We could also retrieve the block argument:
379
581
 
380
- By default, a mock only expects a call. Using `times(1)` is actually a no-op.
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
- mock(object).foo.times(1)
384
- object.foo
385
- object.foo # fails
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
- #### [Expecting method to called exact number of times](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-called-exact-number-of-times)
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
- Times! Which is the same as RR.
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
- mock(object).foo.times(3)
394
- object.foo
395
- object.foo
396
- object.foo
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
- Alternatively, you could also do this. It's exactly the same.
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
- 3.times{ mock(object).foo }
404
- object.foo
405
- object.foo
406
- object.foo
407
- object.foo # fails
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
- #### [Expecting method to be called minimum number of times](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-be-called-minimum-number-of-times)
637
+ #### match
411
638
 
412
- It's not supported in Muack, but we could emulate it somehow:
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
- times = 0
416
- stub(object).foo{ times += 1 }
417
- object.foo
418
- object.foo
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
- #### [Expecting method to be called maximum number of times](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-be-called-maximum-number-of-times)
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
- It's not supported in Muack, but we could emulate it somehow:
656
+ `hash_including` would check if the given hash is the actual
657
+ argument's subset.
426
658
 
427
659
  ``` ruby
428
- times = 0
429
- stub(object).foo{ times += 1; raise "BOOM" if times > 3 }
430
- object.foo
431
- object.foo
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
- #### [Expecting method to be called any number of times](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#expecting-method-to-be-called-any-number-of-times)
666
+ #### including
437
667
 
438
- Just use `stub`, which is exactly why it is designed.
668
+ `including` would check if the actual argument includes the given value
669
+ via `include?` method.
439
670
 
440
671
  ``` ruby
441
- stub(object).foo
442
- object.foo
443
- object.foo
444
- object.foo
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
- #### [Argument wildcard matchers](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#argument-wildcard-matchers)
678
+ #### within
448
679
 
449
- `anything` is the same as RR.
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
- mock(object).foobar(1, anything)
453
- object.foobar(1, :my_symbol)
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
- `is_a` is the same as RR.
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
- mock(object).foobar(is_a(Time))
460
- object.foobar(Time.now)
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
- No numeric supports. Simply use `is_a(Numeric)`
702
+ Note that you could give multiple messages to `respond_to`.
464
703
 
465
704
  ``` ruby
466
- mock(object).foobar(is_a(Numeric))
467
- object.foobar(99)
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
- No boolean supports, but you can use union (`|`).
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
- mock(object).foobar(is_a(TrueClass) | is_a(FalseClass))
474
- object.foobar(false)
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
- Since duck_type is a weird name to me. Here we use `respond_to(:walk, :talk)`.
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
- mock(object).foobar(respond_to(:walk, :talk))
481
- arg = Object.new
482
- def arg.walk; 'waddle'; end
483
- def arg.talk; 'quack'; end
484
- object.foobar(arg)
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
- You can also use intersection (`&`) for multiple responses.
488
- Though there's not much point here. Just want to demonstrate.
738
+ Or boolean, you might say:
489
739
 
490
740
  ``` ruby
491
- mock(object).foobar(respond_to(:walk) & respond_to(:talk))
492
- arg = Object.new
493
- def arg.walk; 'waddle'; end
494
- def arg.talk; 'quack'; end
495
- object.foobar(arg)
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
- Don't pass ranges directly for ranges, use `within`. Or how do we tell
499
- if we really want the argument to be a `Range` object?
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
- mock(object).foobar(within(1..10))
503
- object.foobar(5)
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
- The same goes to regular expression. Use `match` instead.
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
- mock(object).foobar(match(/on/))
510
- object.foobar("ruby on rails")
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
- `hash_including` is the same as RR.
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(object).foobar(hash_including(:red => "#FF0000", :blue => "#0000FF"))
517
- object.foobar({:red => "#FF0000", :blue => "#0000FF", :green => "#00FF00"})
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
- `satisfy` is the same as RR.
814
+ With two instances:
521
815
 
522
816
  ``` ruby
523
- mock(object).foobar(satisfy {|arg| arg.length == 2 })
524
- object.foobar("xy")
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
- #### [Writing your own argument matchers](https://github.com/rr/rr/blob/e4b4907fd0488738affb4dab8ce88cbe9fa6580e/doc/03_api_overview.md#writing-your-own-argument-matchers)
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
- See [`lib/muack.rb`][muack.rb] and [`lib/muack/satisfy.rb`][satisfy.rb],
530
- you would get the idea soon. Here's how `is_a` implemented.
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
- module Muack::API
534
- module_function
535
- def is_a klass
536
- Muack::IsA.new(klass)
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
- class Muack::IsA < Muack::Satisfy
541
- def initialize klass
542
- super lambda{ |actual_arg| actual_arg.kind_of?(klass) }, [klass]
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
- [muack.rb]: https://github.com/godfat/muack/blob/master/lib/muack.rb
548
- [satisfy.rb]: https://github.com/godfat/muack/blob/master/lib/muack/satisfy.rb
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.