+ 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. +
+ +
+
+The mock injection problem
+
+
+ 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. +
++ 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... +
+<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password) { + $socket = &new Socket($ip, $port); + $socket->read( ... ); + ... + } + } +?> ++ We would really like to have a mock object version of the socket + here, what can we do? + +
+ 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. +
++ Here this would be... +
+<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect(&$socket, $username, $password) { + $socket->read( ... ); + ... + } + } +?> ++ This means that the test code is typical for a test involving + mock objects. +
+class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect($socket, 'Me', 'Secret'); + ... + } +} ++ 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. + +
+ The next simplest compromise is to have the created object passed + in as an optional parameter... +
+<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password, $socket = false) { + if (!$socket) { + $socket = &new Socket($ip, $port); + } + $socket->read( ... ); + ... + return $socket; + } + } +?> ++ For a quick solution this is usually good enough. + The test now looks almost the same as if the parameter + was formally passed... +
+class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket); + ... + } +} ++ 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. + +
+ The next method is to pass in a factory object to do the creation... +
+<?php + require_once('socket.php'); + + class Telnet { + function Telnet(&$network) { + $this->_network = &$network; + } + ... + function &connect($ip, $port, $username, $password) { + $socket = &$this->_network->createSocket($ip, $port); + $socket->read( ... ); + ... + return $socket; + } + } +?> ++ 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... +
+class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $network = &new MockNetwork($this); + $network->setReturnReference('createSocket', $socket); + $telnet = &new Telnet($network); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} ++ 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. + +
+ Is there a middle ground? +
+ +
+
+Protected factory method
+
+
+ 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... +
+<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password) { + $socket = &$this->_createSocket($ip, $port); + $socket->read( ... ); + ... + } + + function &_createSocket($ip, $port) { + return new Socket($ip, $port); + } + } +?> ++ This is the only change we make to the application code. + +
+ For the test case we have to create a subclass so that + we can intercept the socket creation... +
+class TelnetTestVersion extends Telnet { + var $_mock; + + function TelnetTestVersion(&$mock) { + $this->_mock = &$mock; + $this->Telnet(); + } + + function &_createSocket() { + return $this->_mock; + } +} ++ 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 + connect(). + Otherwise it could get a null value from + _createSocket(). + +
+ After the completion of all of this extra work the + actual test case is fairly easy. + We just test our new class instead... +
+class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($socket); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} ++ 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? + + + +
+ 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. +
++ Here is the partial mock version of the test... +
+Mock::generatePartial( + 'Telnet', + 'TelnetTestVersion', + array('_createSocket')); + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} ++ The partial mock is a subclass of the original with + selected methods "knocked out" with test + versions. + The generatePartial() call + takes three parameters: the class to be subclassed, + the new test class name and a list of methods to mock. + +
+ 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. +
++ 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. +
++ 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... +
+class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket); + $telnet->expectOnce('_createSocket', array('127.0.0.1', 21)); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + $telnet->tally(); + } +} ++ + +
+
+Testing less than a class
+
+
+ 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. +
++ 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. +
++ It's all going to come down to the coding standards of your + project to decide which mechanism you use. +
+ +