summaryrefslogtreecommitdiff
path: root/test_tools/simpletest/docs/en/partial_mocks_documentation.html
blob: 207494159e7765e5108a38af7b4e10096d35e08d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>SimpleTest for PHP partial mocks documentation</title>
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
</head>
<body>
<div class="menu_back">
<div class="menu">
<h2>
<a href="index.html">SimpleTest</a>
</h2>
<ul>
<li>
<a href="overview.html">Overview</a>
</li>
<li>
<a href="unit_test_documentation.html">Unit tester</a>
</li>
<li>
<a href="group_test_documentation.html">Group tests</a>
</li>
<li>
<a href="server_stubs_documentation.html">Server stubs</a>
</li>
<li>
<a href="mock_objects_documentation.html">Mock objects</a>
</li>
<li>
<span class="chosen">Partial mocks</span>
</li>
<li>
<a href="reporter_documentation.html">Reporting</a>
</li>
<li>
<a href="expectation_documentation.html">Expectations</a>
</li>
<li>
<a href="web_tester_documentation.html">Web tester</a>
</li>
<li>
<a href="form_testing_documentation.html">Testing forms</a>
</li>
<li>
<a href="authentication_documentation.html">Authentication</a>
</li>
<li>
<a href="browser_documentation.html">Scriptable browser</a>
</li>
</ul>
</div>
</div>
<h1>Partial mock objects documentation</h1>
<div class="content">
        
            <p>
                A partial mock is simply a pattern to alleviate a specific problem
                in testing with mock objects,
                that of getting mock objects into tight corners.
                It's quite a limited tool and possibly not even a good idea.
                It is included with SimpleTest because I have found it useful
                on more than one occasion and has saved a lot of work at that point.
            </p>
        
        <p>
<a class="target" name="inject">
<h2>The mock injection problem</h2>
</a>
</p>
            <p>
                When one object uses another it is very simple to just pass a mock
                version in already set up with its expectations.
                Things are rather tricker if one object creates another and the
                creator is the one you want to test.
                This means that the created object should be mocked, but we can
                hardly tell our class under test to create a mock instead.
                The tested class doesn't even know it is running inside a test
                after all.
            </p>
            <p>
                For example, suppose we are building a telnet client and it
                needs to create a network socket to pass its messages.
                The connection method might look something like...
<pre>
<strong>&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...
        function &amp;connect($ip, $port, $username, $password) {
            $socket = &amp;new Socket($ip, $port);
            $socket-&gt;read( ... );
            ...
        }
    }
?&gt;</strong>
</pre>
                We would really like to have a mock object version of the socket
                here, what can we do?
            </p>
            <p>
                The first solution is to pass the socket in as a parameter,
                forcing the creation up a level.
                Having the client handle this is actually a very good approach
                if you can manage it and should lead to factoring the creation from
                the doing.
                In fact, this is one way in which testing with mock objects actually
                forces you to code more tightly focused solutions.
                They improve your programming.
            </p>
            <p>
                Here this would be...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...
        <strong>function &amp;connect(&amp;$socket, $username, $password) {
            $socket-&gt;read( ... );
            ...
        }</strong>
    }
?&gt;
</pre>
                This means that the test code is typical for a test involving
                mock objects.
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new Telnet();
        $telnet-&gt;connect($socket, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                It is pretty obvious though that one level is all you can go.
                You would hardly want your top level application creating
                every low level file, socket and database connection ever
                needed.
                It wouldn't know the constructor parameters anyway.
            </p>
            <p>
                The next simplest compromise is to have the created object passed
                in as an optional parameter...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...<strong>
        function &amp;connect($ip, $port, $username, $password, $socket = false) {
            if (!$socket) {
                $socket = &amp;new Socket($ip, $port);
            }
            $socket-&gt;read( ... );</strong>
            ...
            return $socket;
        }
    }
?&gt;
</pre>
                For a quick solution this is usually good enough.
                The test now looks almost the same as if the parameter
                was formally passed...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret', &amp;$socket);
        ...</strong>
    }
}
</pre>
                The problem with this approach is its untidiness.
                There is test code in the main class and parameters passed
                in the test case that are never used.
                This is a quick and dirty approach, but nevertheless effective
                in most situations.
            </p>
            <p>
                The next method is to pass in a factory object to do the creation...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {<strong>
        function Telnet(&amp;$network) {
            $this-&gt;_network = &amp;$network;
        }</strong>
        ...
        function &amp;connect($ip, $port, $username, $password) {<strong>
            $socket = &amp;$this-&gt;_network-&gt;createSocket($ip, $port);
            $socket-&gt;read( ... );</strong>
            ...
            return $socket;
        }
    }
?&gt;
</pre>
                This is probably the most highly factored answer as creation
                is now moved into a small specialist class.
                The networking factory can now be tested separately, but mocked
                easily when we are testing the telnet class...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $network = &amp;new MockNetwork($this);
        $network-&gt;setReturnReference('createSocket', $socket);
        $telnet = &amp;new Telnet($network);
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                The downside is that we are adding a lot more classes to the
                library.
                Also we are passing a lot of factories around which will
                make the code a little less intuitive.
                The most flexible solution, but the most complex.
            </p>
            <p>
                Is there a middle ground?
            </p>
        
        <p>
<a class="target" name="creation">
<h2>Protected factory method</h2>
</a>
</p>
            <p>
                There is a way we can circumvent the problem without creating
                any new application classes, but it involves creating a subclass
                when we do the actual testing.
                Firstly we move the socket creation into its own method...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...
        function &amp;connect($ip, $port, $username, $password) {<strong>
            $socket = &amp;$this-&gt;_createSocket($ip, $port);</strong>
            $socket-&gt;read( ... );
            ...
        }<strong>
        
        function &amp;_createSocket($ip, $port) {
            return new Socket($ip, $port);
        }</strong>
    }
?&gt;
</pre>
                This is the only change we make to the application code.
            </p>
            <p>
                For the test case we have to create a subclass so that
                we can intercept the socket creation...
<pre>
<strong>class TelnetTestVersion extends Telnet {
    var $_mock;
    
    function TelnetTestVersion(&amp;$mock) {
        $this-&gt;_mock = &amp;$mock;
        $this-&gt;Telnet();
    }
    
    function &amp;_createSocket() {
        return $this-&gt;_mock;
    }
}</strong>
</pre>
                Here I have passed the mock in the constructor, but a
                setter would have done just as well.
                Note that the mock was set into the object variable
                before the constructor was chained.
                This is necessary in case the constructor calls
                <span class="new_code">connect()</span>.
                Otherwise it could get a null value from
                <span class="new_code">_createSocket()</span>.
            </p>
            <p>
                After the completion of all of this extra work the
                actual test case is fairly easy.
                We just test our new class instead...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($socket);
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                The new class is very simple of course.
                It just sets up a return value, rather like a mock.
                It would be nice if it also checked the incoming parameters
                as well.
                Just like a mock.
                It seems we are likely to do this often, can
                we automate the subclass creation?
            </p>
        
        <p>
<a class="target" name="partial">
<h2>A partial mock</h2>
</a>
</p>
            <p>
                Of course the answer is "yes" or I would have stopped writing
                this by now!
                The previous test case was a lot of work, but we can
                generate the subclass using a similar approach to the mock objects.
            </p>
            <p>
                Here is the partial mock version of the test...
<pre>
<strong>Mock::generatePartial(
        'Telnet',
        'TelnetTestVersion',
        array('_createSocket'));</strong>

class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($this);
        $telnet-&gt;setReturnReference('_createSocket', $socket);
        $telnet-&gt;Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                The partial mock is a subclass of the original with
                selected methods "knocked out" with test
                versions.
                The <span class="new_code">generatePartial()</span> call
                takes three parameters: the class to be subclassed,
                the new test class name and a list of methods to mock.
            </p>
            <p>
                Instantiating the resulting objects is slightly tricky.
                The only constructor parameter of a partial mock is
                the unit tester reference.
                As with the normal mock objects this is needed for sending
                test results in response to checked expectations.
            </p>
            <p>
                The original constructor is not run yet.
                This is necessary in case the constructor is going to
                make use of the as yet unset mocked methods.
                We set any return values at this point and then run the
                constructor with its normal parameters.
                This three step construction of "new", followed
                by setting up the methods, followed by running the constructor
                proper is what distinguishes the partial mock code.
            </p>
            <p>
                Apart from construction, all of the mocked methods have
                the same features as mock objects and all of the unmocked
                methods behave as before.
                We can set expectations very easily...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($this);
        $telnet-&gt;setReturnReference('_createSocket', $socket);<strong>
        $telnet-&gt;expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
        $telnet-&gt;Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...<strong>
        $telnet-&gt;tally();</strong>
    }
}
</pre>
            </p>
        
        <p>
<a class="target" name="less">
<h2>Testing less than a class</h2>
</a>
</p>
            <p>
                The mocked out methods don't have to be factory methods,
                they could be any sort of method.
                In this way partial mocks allow us to take control of any part of
                a class except the constructor.
                We could even go as far as to mock every method
                except one we actually want to test.
            </p>
            <p>
                This last situation is all rather hypothetical, as I haven't
                tried it.
                I am open to the possibility, but a little worried that
                forcing object granularity may be better for the code quality.
                I personally use partial mocks as a way of overriding creation
                or for occasional testing of the TemplateMethod pattern.
            </p>
            <p>
                It's all going to come down to the coding standards of your
                project to decide which mechanism you use.
            </p>
        
    </div>
<div class="copyright">
            Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
        </div>
</body>
</html>