Mock objects have been getting heavy use in the unit tests of a project I am currently working on. As another tool in the testing arsenal they allow me to make sure that the interactions of the component under test are behaving as expected.
As mentioned in my last post I took to refining our library for creating mock objects by writing many more tests, ensuring they pass, hiding implementation details behind namespaces, adding additional features, and rinsing and repeating. And now its time for you to make use of it too.
To get you started here are some usage examples. Or jump straight to the source.
- expect a method and return a value
mock.method( 'gimmeTruth' ).returns( true );
trace( mock.gimmeTruth() ); // true
- expect a method and throw an error
mock.method( 'complainLoudly' ).throws( new Error( 'How rude!' ) );
try{ mock.complainLoudly(); } catch( e:Error ){ trace( e.message ); } // 'How rude!'
- expect a property and return a value
mock.property( 'gimmeNumber' ).returns( 10 );
trace( mock.gimmeNumber ); // 10
- argument expectations
// single literal value
mock.method('icanhasone').withArgs( 1 );
mock.icanhasone(0); // MockExpectationError thrown, no matching method for that argument
mock.iconhasone(1); // accepted silently
// multiple literal values
mock.method('sequence').withArgs( 3, 5, 8, 13, 21 );
mock.sequence( 3, 5, 8, 13, 21 ); // accepted silently
// class instances
mock.method('eatDonut').withArg( Donut );
mock.eatDonut( new FrenchFries() ); // throws MockExpectationError
mock.eatDonut( new Donut() ); // yum...
// functions
var anyOf:Function = function( values:Array ):Function
{
return function( value:Object ):Boolean { return values.indexOf( value ) != -1; };
}
mock.method('threes').withArgs( anyOf(3, 9, 27), anyof(4, 8, 12) );
mock.threes( 8, 9 ); // throw MockExpectationError
mock.threes( 9, 8 ); // accepted silently
All these examples are well and good if the component you are testing is happy to accept an Object, however we are often coding to an interface or the component is expecting an instance of a specific class. At this stage we cannot just pass in the instance of Mock, we need to wrap it in the class or interface. We do this by stubbing the method to pass the arguments to the mock instance and returning values from the mock back out.
// the interface we are going to implement with our mock
package concrete.example
{
public interface Example
{
function acceptNumber( value:Number ):void;
function giveString():String;
}
}
// our mock class
package mock.example
{
import com.anywebcam.mock.*;
import concrete.example.Example;
public class MockExample implements Example
{
public var mock:Mock;
public function MockExample()
{
mock = new Mock( this );
}
public function acceptNumber( value:Number ):void
{
mock.acceptNumber( value );
}
public function giveString():String
{
return mock.giveString();
}
}
}
// our component we are going to test
package concrete.example
{
public class SomeComponent
{
public var myExample:Example;
public function doSomethingWithExample( value:Number ):String
{
myExample.acceptNumber( value );
return myExample.giveString();
}
}
}
// then in our test
public function testSomeComponentBehaves():void
{
var c:SomeComponent = new SomeComponent();
var e:MockExample = new MockExample();
e.mock.method('acceptNumber').withArgs( 10 );
e.mock.method('giveString').returns( 'ten' ):
var retval:String = c.doSomethingWithExample(e);
assertEquals( 'ten', retval );
}
For a non-trivial interfaces and classes creating these stubs manually is a pain in the arse. Currently I use couple of textmate snippets to help automate some of the process. In the future I would like to see them generated by the library either at compile time or at run time (and yes there are plans on how to achieve it both ways).
Grab the source from the mock-as3 Google Code project via:
svn co http://mock-as3.googlecode.com/svn/trunk/ mock-as3
API Docs are currently in the repository, and will be published on here tomorrow.