Every repository with this icon (
Every repository with this icon (
Mock Objects
Introduction to Mock Objects and Isolation Testing
There will come a time in unit testing where object Foo needs to work with object Bar. For any one Unit Test Case, we only want to test a single class, and not its dependents; we call this “isolation testing”.,,1,, In the example given, we want to test Foo, without testing Bar. This is possible with a Mock object.
In SnapTest, Mock Objects are objects that behave exactly like the object they derive from. They should satisfy the Liskov’s Substitution Principle,,2,, and be interchangable with the class in which they mock. SnapTest lets mock objects be created through two main ways:
NOTE: SnapTest 1.1 and 1.2 treat mock objects based on existing classes differently. Mock objects in 1.2 will inherit from the class, while in 1.1 they did not.
- A mock based on an Interface class – the mock will implement the interface and will provide all required methods.
- A mock based on and extending an existing class – the mock will implement all public and protected methods, and will inherit from the original class. By default, all of the methods will be “knocked out” and not have return values to promote isolation testing.
In most cases, the first example based on an Interface class is the ideal solution. This promotes a loosely coupled architecture with an emphasis on objects owning each other as opposed to extension.
Mock Objects as Actors and Critics
Mock objects can serve two roles in the code: actor and critic. As an actor, they are responsible for simulating a valid return for each method call. As a critic, they are responsible for tracking the number of calls that are made to ensure accurate reporting. Both roles are important- it depends on what the test is looking for.
Creating a Mock Object
Mock objects are available in any Snap_UnitTestCase, or any file that includes the mock.php file.
public function setUp() {
$foo = $this->mock('Foo')->construct();
}
// alternative: when not using snap test cases
$foo = new Snap_MockObject('Foo');
$foo = $foo->construct();
Mock objects use method chaining to make it easier to string together returns in a readable format. Usually, several operations will be done on a mock object in a row, in order to set it up to behave a certain way.
A mock object is finished off with a call to construct(), which creates the mock object. If you have inherited from a parent object, parameters passed to construct will be passed to __construct() in the parent.
Mock By Interface
public function setUp() {
$foo = $this->mock('Foo_Interface')->construct();
}
Mock By Cloning
public function setUp() {
$tastes_like_a_foo = $this->mock('Foo')->construct();
}
Mock By Cloning, Implementing an Interface
public function setUp() {
$foo = $this->mock('Foo')
->requiresInterface('Foo_Interface')
->construct();
}
Note: This example uses the method chaining. Because the object returned from $this->mock() is the mock object setup, additional methods can be called on it to create a sequence of statements. This line can alternatively be written as three lines:
public function setUp() {
$foo = $this->mock('Foo')
$foo->requiresInterface('Foo_Interface');
$foo = $foo->construct();
}
Method chaining is preferred when the mock object setup is straightforward.
Mock By Extension
Note: As a reminder, it should be said that mocking by extension makes a situation that is much more difficult to do isolation testing with. However, mocking by extension is very useful when you want to have control over the object you are testing (for example, knocking out a factory method).
public function setUp() {
$foo = $this->mock('Foo')
->requiresInheritance()
->construct();
}
Using Mock Objects as Actors
Mock objects can be used as actors, returning specific values (just like the real object would), and can return different values based on what parameters were passed in, how many times the method has been called, or just a default value.
public function setUp() {
$foo = $this->mock('Foo')
->setReturnValue('barMethod', 'myreturnstring')
->setReturnValueAt('bazMethod', 3, 'myotherstring')
->setReturnValue('quuxMethod', 'specialstring', array(
new Snap_Anything_Expectation(),
new Snap_Object_Expectation('My_Custom_Class'),
))
->construct();
}
Both setReturnValue() and setReturnValueAt are relatively straightforward. When the method (first parameter) is called, the return value is returned. For setReturnValueAt, the second parameter dictates on what ‘call’ to the method the return value is to be returned.
The final parameter in the third return value represents the parameters passed to the function. These are Expectations (see Expectations Reference). Any time you have a set of parameters you can use as criteria, you can optionally pass in a Snap_Expectation object. So, in the third example, a call to $object->quuxMethod() will only return ‘specialstring’ when a first parameter is anything, and a second parameter is an object of type ‘My_Custom_Class’.
As a guiding principle, return values should only be as specific as they need to be to validate a test. Too granular, and you create a brittle test. Too broad, and the mock won’t behave close enough to the real object.
Using Mock Objects as Critics
Mock objects can also be used as critics, listening to and reporting on any methods that were called. This makes it possible in a unit test to ask a mock object how many times “fooMethod” was called. This is especially handy in controlled loops, when you want to ensure certain operations don’t run out of control.
public function setUp() {
$foo = $this->mock('Foo')
->listenTo('fooMethod')
->listenTo('barMethod', array(
new Snap_Anything_Expectation(),
new Snap_Object_Expectation('My_Custom_Class'),
))
->construct();
}
The listenTo method doesn’t affect return values, but instead ensures that the method (first parameter) is listened to. Like the Actor methods, the second parameter is an array that represents expectations (see Exepctations Reference?) for the incoming parameters.
Note: Any object that has an actor method placed on it (setReturnValue(), setReturnValueAt()) is automatically listened to as well. In those cases, adding a listenTo() call would be redundant, but in no way harmful.
An object with critics can have assertions made, see AssertionReference in the section “Call Count Assertions” for how to assert a method was called a certain number of times.
Mock Object Reference
| methodName($params, [$optional]) | Description |
|---|---|
| $this→mock($class_name) | Creates a mock-construction object based on $class_name. Will also work with an interface name. (Snap_UnitTestCase specific) |
| $mock→requiresInheritance() | Tells a cloned mock object that it needs to inherit from the parent class it is based on. |
| $mock→requiresInterface($iface_name) | Tells a cloned mock object to implement the specified $iface_name. |
| $mock→setReturnValue($method, $return, [$params]) | Tells the mock object to return $return when $method is called. If $params is specified, each expectation must be met. |
| $mock→setReturnValueAt($method, $at, $return, [$params]) | Similar to setReturnValue() but with an additional parameter for when to return $return. If $params is set, each expectation must be met. |
| $mock→listenTo($method, [$params]) | Adds a listener to $method, so that the mock object can act as a critic. See AssertionReference for call count assertions. If $params is set, each expectation must be met. |
| $mock→construct([$params…n]) | Constructs the mock object, and passes N $params through to the parent if the mock object setup requires it. Until construct() is called, the mock will not behave like the object it is mocking. |
About $params: in the methods $this->setReturnValue(), $this->setReturnValueAt(), and $this->listenTo(), all accept an optional $params argument. Specified as an array, $params contains a list of parameter expectations (see Expectation Reference?) that should be fulfilled. If every expectation comes up as a match, then the method’s assignment will be carried out. In the case of multiple results for $this->setReturnValue() and $this->setReturnValueAt(), an exception will be thrown.
References
- (1) http://www.theserverside.com/tt/articles/article.tss?l=JUnitInAction
- (2) http://javaboutique.internet.com/tutorials/JavaOO/






