help  namespaces  categories  help  rss feeds

<< Back to Dashboard

Contents

MockBox: The ColdBox Mocking/Stubbing Framework

Covers up to version 2.0
logo

Introduction

MockBox is a companion package to the ColdBox Platform that will give you advanced mocking/stubbing capabilities; hence a Mocking Framework. Not only does it integrate into the ColdBox unit testing framework powered by MXUnit, but it can also be used as a standalone mocking/stubbing framework for ANY type of stubbing/mocking you would like to in any ColdFusion application.

Download

Download from our downloads page or clone via github:

git clone git://github.com/ColdBox/coldbox-platform.git coldbox

Resources

System Requirements

MockBox has been designed to work under the following CFML Engines:

  • Railo 3.1 and beyond
  • Adobe ColdFusion 8 and beyond (7 with certain restrictions)
  • Open Blue Dragon

Other Requirements:

  • You can use MockBox as a standalone framework in which the starting component namespace will be /mockbox/system/. However, if you use MockBox within the ColdBox core, the starting component namespace will be /coldbox/system/. Take this into consideration for all the samples, configurations and cfc paths.
  • Write capabilities on disk for the default path of /{coldbox or mockbox}/system/testings/stubs. You can also choose the directory destination for stub creations yourself when you initialize MockBox. If using ColdFusion 9 or Railo you can even use ram:// and use the virtual file system.

What is Mocking?

"A mock object is an object that takes the place of a 'real' object in such a way that makes testing easier and more meaningful, or in some cases, possible at all". by Scott Bain (Emergent Design - The Evolutionary Nature of Professional Software Development)

Here are some examples of real life mocks to get you in the mocking mood:

When doing unit testing of ColdFusion CFCs, we will come to a point where a single class can have multiple external dependencies or collaborators; whether they are classes themselves, data, external APIs, etc. Therefore, in order to unit test our class exclusively and easily we need to be able to mock this behavior or have full control of this behavior. Remember that unit testing is the testing of software units in isolation. If not, we would be instantiating and creating entire set of components, frameworks, pulling network plugs and so much more ridiculous but functional things just to test one single piece of functionality and/or behavior. So in summary, mock objects are just test oriented replacements for collaborators and dependencies.

Mock objects can also be created by hand, but MockBox takes this pain away by leveraging dynamic techniques so that you can Mock dynamically and at runtime. Like Scott Bain describes in his Emergent Design book:

"Mocks are definitely congruent with the Gang of Four (GoF) notion of designing to interfaces, because a mock is essentially the interface without any real implementation." - Scott Bain (Emergent Design)

You will be leveraging MockBox to create objects that represent your dependencies or even data, decide what methods will return (expectations), mock network connections, exceptions and much more. You can then very easily test the exclusive behavior of components as you will now have control of all expectations, and remember that testing is all about expectations. Also, as your object oriented applications get more complex, mocking becomes essential, but you have to be aware that there are limitations. Not only will you do unit-testing but you will need to expand to do integration testing to make sure the all encompassing behavior is still maintained. However, by using a mocking framework like MockBox you will be able to apply a test-driven development methodology to your unit-testing and be able to accelerate your development and testing. The more you mock, the more you will get a feel for it and find it completely essential when doing unit testing. Welcome to a world where mocking is fun and not frowned upon :)

Our Approach, Features and Benefits

The approach that we take with MockBox is a dynamic and minimalistic approach. Why dynamic? Well, because we dynamically transform target objects into mock form at runtime. The API for the mocking factory is very easy to use and provides you a very simplistic approach to mocking. We even use $() style method calls so you can easily distinguish when using or mocking methods, properties, etc. So what can MockBox do for me?

  • Create mock objects for you and keep their methods intact (Does not wipe methods, so you can do method spys, or mock helper methods)
  • Create mock objects and wipe out their method signatures
  • Create stub objects for objects that don't even exist yet. So you can build to interfaces and later build dependencies.
  • Decorate instantiated objects with mocking capabilities (So you can mock targeted methods and properties; spys)
  • Mock internal object properties, basically do property injections in any internal scope
  • State-Machine Results. Have a method recycle the results as it is called consecutively. So if you have a method returning two results and you call the method 4 times, the results will be recycled: 1,2,1,2
  • Method call counter, so you can keep track of how many times a method has been called
  • Method arguments call logging, so you can keep track of method calls and their arguments as they are called. This is a great way to find out what was the payload when calling a mocked method
  • Ability to mock results depending on the argument signatures sent to a mocked method with capabilities to even provide state-machine results
  • Ability to mock private/package methods
  • Ability to mock exceptions from methods or make a method throw a controlled exception
  • Ability to change the return type of methods or preserve their signature at runtime, extra cool when using stubs that still have no defined signature
  • Ability to call a debugger method ($debug()) on mocked objects to retrieve extra debugging information about its mocking capabilities and its mocked calls

Our approach and inspiration was due to Brian Kotek's ColdMock Project, MXUnit's MightyMock framework, Easymock, and Mockito. The MXUnit team rocks!

Creating MockBox

If you are basing your testing on the ColdBox base test case (coldbox.system.testing.BaseTestCase), then you already have MockBox created for you. You can just retrieve it by using the getMockBox() method from your unit tests. However, if you are using MockBox standalone, then you can instantiate the factory like so:

// Within ColdBox Core
mockBox = createObject("component","coldbox.system.testing.MockBox").init();

//StandAlone
mockBox = createObject("component","mockbox.system.testing.MockBox").init();

The factory takes in one constructor argument that is not mandatory: generationPath. This path is a relative path of where the factory generates internal mocking stubs that are included later on at runtime. Therefore, the path must be a path that can be used using cfinclude. The default path the mock factory uses is the following, so you do not have to specify one, just make sure the path has WRITE permissions:

/coldbox/system/testing/stubs
or
/mockbox/system/testing/stubs

Creating a Mock Object

In order to create a mock object you need to use any of the following methods: createMock(), createEmptyMock(), or prepareMock().

createMock()

Used to create a new mock object from scratch or from an already instantiated object.

public any createMock([string CLASSNAME], [any OBJECT], [boolean CLEARMETHODS='false'], [boolean CALLLOGGING='true'])

Parameters:

  • className - The class name of the object to create and mock
  • object - The instantiated object to add mocking capabilities to, similar to using prepareMock()
  • clearMethods - If true, all methods in the target mock object will be removed. You can then mock only the methods that you want to mock
  • callLogging - Add method call logging for all mocked methods only
collaborator = mockbox.createMock("model.myClass");

createEmptyMock()

Used to create a new mock object with all its method signatures wiped out, basically an interface with no real implementation. It will be up to you to mock all behavior.

public any createEmptyMock(string CLASSNAME, [any OBJECT], [boolean CALLLOGGING='true'])

Parameters:

  • className - The class name of the object to create and mock
  • object - The instantiated object to add mocking capabilities to, similar to using prepareMock()
  • callLogging - Add method call logging for all mocked methods only
user = mockbox.createEmptyMock("model.User");

prepareMock()

Decorate an already instantiated object with mocking capabilities. It does not wipe out the object's methods or signature, it only decorates it (mixes-in methods) with methods for mocking operations. This is great for doing targeted mocking for specific methods, private methods, properties and more.

public any prepareMock([any OBJECT], [boolean CALLLOGGING='true'])

Parameters:

  • object - The already instantiated object to prepare for mocking
  • callLogging - Add method call logging for all mocked methods only
myService = createObject("component","model.services.MyCoolService").init();
// prepare it for mocking
mockBox.prepareMock( myService );

Important : If call logging is turned on, then the mock object will keep track of all method calls to mocked methods ONLY. It will store them in a sequential array with all the arguments the method was called with (named or ordered). This is essential if you need to investigate if a method was called and with what arguments. You can also use this to inspect save or update calls based on mocked external repositories.

Sample:

Let's say that we have a user service layer object that relies on the following objects:

  • sessionstorage - a session facade object
  • transfer - the transfer ORM
  • userDAO - a data access object for complex query operations

We can start testing our user service and mocking its dependencies by preparing it in a test case CFC with the following setup() method:


component extends=”mxunit.framework.TestCase” {
  
	function setup(){
		// create MockBox
		mockBox = createobject("component","coldbox.system.testing.MockBox").init();
    
		//Create the User Service to test, do not remove methods, just prepare for mocking.
		userService = mockBox.createMock("model.UserService");
		
		// Mock the session facade, I am using the coldbox one, it can be any facade though
		mockSession= mockBox.createEmptyMock(className='coldbox.system.plugins.SessionStorage');
		
		// Mock Transfer
		mockTransfer = mockBox.createEmptyMock(className='transfer.com.Transfer');
		
		// Mock DAO
		mockDAO = mockBox.createEmptyMock(className='model.UserDAO');
		
		//Init the User Service	with mock dependencies
		userService.init(mockTransfer,mockSession,mockDAO);	
	}
	
	function testGetData(){
		// mock a query using mockbox's querysimulator
		mockQuery = mockBox.querySim("id, name
		1|Luis Majano
		2|Alexia Majano");
		
		// mock the DAO call with this mocked query as its return
		mockDAO.$("getData", mockQuery);
		// call it
		data = userService.getData();
		// assert it
		assertEquals( mockQuery, data );
	}
	
}

The service CFC we just injected mocked dependencies:

<cfcomponent name="UserService" output="False">

<cffunction name="init" returntype="UserService" output="False">
  <cfargument name="transfer">
  <cfargument name="sessionStorage">
  <cfargument name="userDAO">
  <cfscript>
    instance.transfer = arguments.transfer;
    instance.sessionStorage = arguments.sessionStorage;
    instance.userDAO = arguments.userDAO;
    
    return this;  
  </cfscript>
</cffunction>

<cffunction name="getData" returntype="query" output="false">
	<cfreturn instance.userDao.getData()>
</cffunction>

</cfcomponent>

Creating a Stub Object

In order to create a stub object you will use the : createStub() method.

public any createStub([boolean callLogging='true'], [extends], [implements])

Parameters:

  • callLogging - Add method call logging for all mocked methods
  • extends - Make the stub extend from certain CFC
  • implements - Make the stub adhere to an interface(s)

This method will create an empty stub object that you can use and mock with methods and properties. It can then be used in any code to satisfy dependencies meanwhile you build them. This is great to start working on projects where you are relying on other teams to build functionality but you have agreed on specific data or code interfaces. It is also super fancy as it can allow you to tell the stub to inherit from another CFC and look like it, or even pass in one or more interfaces that it must implement. If they are interfaces, then MockBox will generate all the necessary methods to satisfy those interfaces.

CreateStub() Inheritance

The createStub() method has an argument called extends that accepts a class path. This will create and generate a stub that physically extends that class path directly. This is an amazing way to create stubs that you can override with inherited functionality or just make it look like it is EXACTLY the type of object you want.

myService = mockbox.createStub(extends="model.security.MyService");

CreateStub() Interfaces

The createStub() method has an argument called implements that accepts a list of interface class paths you want the stub to implement. MockBox will then generate the stub and it will make sure that it implements all the methods the interfaces have defined as per their contract. This is such a fantastic and easy way to create a stub that looks and feels and actually has the methods an interface needs.

myFakeProvider = mockbox.createStub(implements="coldbox.system.cache.ICacheProvider");
myFakeProvider.getName();

Mocking Methods

Once you have created a mock object, you can use it like the real object as it will respond exactly as it was coded. However, you can override its behavior by using the mocking methods that have been placed on the mocked object at runtime. The methods that you can call upon in your object are the following (we will review them in detail later):

Method Name Return Type Description
$() The Mock Used to mock a method on the mock object that can return, throw or be a void method.
$property() The Mock Mock a property in the object on any scope.
$getProperty(name, scope) any Retrieve any public or private internal state variable so you can do assertions and more mocking.
$results() The Mock Mock 1 or more results of a mock method call, must be chained to a $() or $().$args() call
$args() The Mock Mock 1 or more arguments in sequential or named order. Must be called concatenated to a $() call and must be followed by a concatenated $results() call so the results are matched to specific arguments.
querySim() query to denote columns. Ex: id, name 1 | Luis Majano 2 | Joe Louis

$()

This is the method that you will call upon in order to mock a method's behavior and return results. This method has the capability of mocking a return value or even making the method throw a controlled exception. By default the mocked method results will be returned all the time the method is called. So if the mocked method is called twice, the results will always be returned.

any $(string method, [any returns], boolean preserveReturnType='true', [boolean throwException='false'], [string throwType=], [string throwDetail=], [string throwMessage=], [boolean callLogging='false'])

Parameters:

  • method - The method you want to mock
  • returns - The results it must return, if not passed it returns void or expected to use the mockResults method. (Optional)
  • preserveReturnType - If false, the mock will make the returntype of the method equal to ANY, if not it preserves the original return type.
  • throwException - If you want the method call to throw an exception (Defaults to false)
  • throwType - The type of the exception to throw
  • throwDetail - The detail of the exception to throw
  • throwMessage - The message of the exception to throw
  • callLogging - Will add the machinery to also log the incoming arguments to each subsequent calls to this method

The cool thing about this method is that it also returns the same instance of the object. Therefore, you can use it to chain calls to the object and do multiple mocking of methods all within the same line. Remember that if no returns argument is provided then the return is void

function setup(){
	mockUser = getMockBox().createMock("model.security.User");
    
    //Mock several methods all in one shot!
    mockUser.$("isFound",false).$("isDirty",false).$("isSaved",true);
}

Examples

Let's do some samples now.

//make exists return true in a mocked session object
mockSession.$(method="exists",returns=true);
assertTrue(mockSession.exists('whatevermanKey'));

//make exists return true and then false and then repeat the sequence
mockSession.$(method="exists").$results(true,false);
assertTrue( mockSession.exists('yeaaaaa') );
assertFalse( mockSession.exists('nada') );

//make the getVar return a mock User object
mockUser = getMockBox().createMock(className="model.User");
mockSession.$(method="getVar",results=mockUser);

assertEquals( mockUser, mockSession.getVar('sure') );

//Make the call to user.checkPermission() throw an invalid exception
mockUser.$(method="checkPermission",
		throwException=true,
		throwType="InvalidPermissionException",
		throwMessage="Invalid permission detected",
		throwDetail="The permission you sent was invalid, please try again.");

try{
	mockUser.checkPermission('invalid');
}
catch(Any e){
	if( e.type neq "InvalidPermissionException"){
	  fail('The type was invalid #e.type#');
	}
}

//mock a method with call logging
mockSession.$(method="setVar",callLogging=true);
mockSession.setVar("Hello","Luis");
mockSession.setVar("Name","luis majano");
//dump the call logs
<cfdump var="#mockSession.$callLog()#">

$property()

This method is used in order to mock an internal property on the target object. Let's say that the object has a private property of userDAO that lives in the variables scope and the lifecycle for the object is controlled by its parent, in this case the user service. This means that this dependency is created by the user service and not injected by an external force or dependency injection framework. How do we mock this? Very easily by using the $property() method on the target object.

any $property(any obj, string propertyName, [string propertyScope='variables'], any mock)

Parameters:

  • propertyName - The name of the property to mock
  • propertyScope - The scope where the property lives in. The default is variables scope.
  • mock - The object or data to inject and mock
//decorate our user service with mocking capabilities, just to show a different approach
userService = getMockBox().prepareMock( createObject("component","model.UserService") );

//create a mock dao and mock the getUsers() method
mockDAO=getMockBox().createEmptyMock("model.UserDAO").$("getUsers",QueryNew(""));

//Inject it as a property of the user service, since no external injections are found. variables scope is the default.
userService.$property(propertyName="userDAO",mock=mockDAO);

//Test a user service method that uses the DAO
results = userService.getUsers();
assertTrue( isQuery(results) );

Not only can you mock properties that are objects, but also mock properties that are simple/complex types. Let's say you have a property in your target object that controls debugging and by default the property is false, but you want to test the debugging capabilities of your class. So we have to mock it to true now, but the property exists in variables.instance.debugMode? No problem mate (Like my friend Mark Mandel says)!

//decorate the cache object with mocking capabilties
cache = getMockBox().createMock(object=createObject("component","MyCache"));

//mock the debug property
cache.$property(propertyName="debugMode",propertyScope="instance",mock=true);

$getProperty()

This method can help you retrieve any public or private internal state variable so you can do assertions. You can also pass in a scope argument so you can not only retrieve properties from the variables scope but from any nested structure inside of any private scope:

any $getProperty(name [scope='variables']

Parameters:

  • name - The name of the property to retrieve
  • scope - The scope where the property lives in. The default is variables scope.
assertEquals( 4, model.$getProperty("dataNumber", "variables") );
assertEquals( "Luis", model.$getProperty("name", "variables.instance") );

$results()

This method can only be used in conjunction with $() as a chained call as it needs to know for what method are the results for.

$(...).$results(...)

The purpose of this method is to make a method return more than 1 result in a specific repeating sequence. This means that if you set the mock results to be 2 results and you call your method 4 times, the sequence will repeat itself 1 time. MUMBO JUMBO, show me!! Ok Ok, hold your horses.

//Mock 3 values for the getSetting method
controller.$("getSetting").$results(true,"cacheEnabled","myapp.model"); 

//Call getSetting 1
<cfdump var="#controller.getSetting()#">
Results = true

//Call getSetting 2
<cfdump var="#controller.getSetting()#">
Results = "cacheEnabled"

//Call getSetting 3
<cfdump var="#controller.getSetting()#">
Results = "myapp.model"

//Call getSetting 4
<cfdump var="#controller.getSetting()#">
Results = true

//Call getSetting 5
<cfdump var="#controller.getSetting()#">
Results = "cacheEnabled"

As you can see, the sequence repeats itself once the call counter increases. Let's say that you have a test where the first call to a user object's isAuthorized() method is false but then it has to be true. Then you can do this:

mockUser = getMockBox().createMock("model.User");
mockUser.$("isAuthorized").$results(false,true);

$args()

This method is used to tell MockBox that you want to mock a method with a SPECIFIC number of argument calls. Then you will have to set the return results for it, but this is absolutely necessary if you need to test an object that makes several method calls to the same method with different arguments, and you need to mock different results coming back. Example, let's say you are using a ColdBox configuration bean that holds configuration data. You make several calls to the getKey() method with different arguments:

configBean.getKey('DebugMode');
configBean.getKey('OutgoingMail');

How in the world can I mock ths? Well, using the mock arguments method.

//get a mock config bean
mockConfig = getMockBox().createEmptyMock("coldbox.system.beans.ConfigBean");
//mock the method with args
mockConfig.$("getKey").$args("debugmode").$results(true);
mockConfig.$("getKey").$args("OutgoingMail").$results('devmail@mail.com');

//Then you can call and get the expected results

So remember that if you use the $args() call, you need to tell it what kind of results you are expecting by calling the $results() method after it or you might end up with an exception.

querySim()

This method is NOT injected into mock objects but avaialble via MockBox directly in order to create queries very quickly. This is a great way to simulate cfquery calls, cfdirectory or any other cf tag the returns a query.

function testGetUsers(){
	// Mock a query
	mockQuery = mockBox.querySim("id,fname,lname
	1 | luis | majano
	2 | joe | louis
	3 | bob | lainez");
	
	// tell the dao to return this query
	mockDAO.$("getUsers", mockQuery);	
}

Verification Methods

The following methods are also mixed in at runtime into mock objects and they will be used to verify behavior and calls from these mock/stub objects. These are great in order to find out how many mocked methods calls have been made and to find out the arguments that where passed to each mocked method call.

Method Name Return Type Description
$count([methodName]) Numeric Get the number of times all mocked methods have been called on a mock or pass in a method name and get a the method's call count.
$times(count,[methodName]) or $verifyCallCount(count,[methodName]) Numeric Assert how many calls have been made to the mock or a specific mock method
$never([methodName]) Boolean Assert that no interactions have been made to the mock or a specific mock method: Alias to $times(0)
$atLeast(minNumberOfInvocations,[methodName]) Boolean Assert that at least a certain number of calls have been made on the mock or a specific mock method
$once([methodName]) Boolean Assert that only 1 call has been made on the mock or a specific mock method
$atMost(maxNumberOfInvocations, [methodName]) Boolean Assert that at most a certain number of calls have been made on the mock or a specific mock method.
$callLog() struct Retrieve the method call logger structure of all mocked method calls.
$reset() void Reset all mock counters and logs on the targeted mock.
$debug() struct Retrieve a structure of mocking debugging information about a mock object.

$count()

Get the number of times a method has been called or the entire number of calls made to ANY mocked method on this mock object. If the method has never been called, you will receive a 0. If the method does not exist or has not been mocked, then it will return a -1.

numeric $count([string methodName])

Parameters:

  • methodName - Name of the method to get the counter for (Optional)
mockUser = getMockBox().createMock("model.User");
mockUser.$("isAuthorized").$results(false,true);

debug(mockUser.$count("isAuthorized"));
//DUMPS 0

mockUser.isAuthorized();
debug(mockUser.$count("isAuthorized"));
//DUMPS 1

mockUser.isAuthorized();
debug(mockUser.$count("isAuthorized"));
//DUMPS 2

// dumps 2 also
debug( mockUser.$count() );

$times() or $verifyCallCount()

This method is used to assert how many times a mocked method has been called or ANY mocked method has been called.

Boolean $times(numeric count, [methodname])

Parameters:

  • count - The number of times any method or a specific mocked method has been called
  • methodName - The optional method name to assert the number of method calls

Examples:

security = getMockBox().createMock("model.security");

//No calls yet
assertTrue( security.$times(0) );

security.$("isValidUser",false);
security.isValidUser();

// Asserts
assertTrue( security.$times(1) );
assertTrue( security.$times(1,"isValidUser") );

security.$("authenticate",true);
security.authenticate("username","password");

assertTrue( security.$times(2) );
assertTrue( security.$times(1,"authenticate") );

$never()

This method is a quick notation for the $times(0) call but more expressive when written in code:

Boolean $never([methodname])

Parameters:

  • methodName - The optional method name to assert the number of method calls

Examples:

security = getMockBox().createMock("model.security");

//No calls yet
assertTrue( security.$never() );

security.$("isValidUser",false);
security.isValidUser();

// Asserts
assertFalse( security.$never("isValidUser") );

$atLeast()

This method can help you verify that at least a minimum number of calls have been made to all mocked methods or a specific mocked method.

Boolean $atLeast(minNumberOfInvocations,[methodname])

Parameters:

  • minNumberOfInvocations - The min number of calls to assert
  • methodName - The optional method name to assert the number of method calls

Examples:

// let's say we have a service that verifies user credentials
// and if not valid, then tries to check if the user can be inflated from a cookie
// and then verified again
function verifyUser(){
	
	if( isValidUser() ){
		log.info("user is valid, doing valid operations");	
	}	
	
	// check if user cookie exists
	if( isUserCookieValid() ){
		// inflate credentials
		inflateUserFromCookie();
		// Validate them again
		if( NOT isValidUser() ){
			log.error("user from cookie invalid, aborting");
		}	
	}
}

// Now the test
function testVerifyUser(){
	
	security = getMockBox().createMock("model.security").$("isValidUser",false);
	security.storeUserCookie("invalid");
	security.verifyUser();
	
	// Asserts that isValidUser() has been called at least 5 times
	assertFalse( security.$atLeast(5,"isValidUser") );
	// Asserts that isValidUser() has been called at least 2 times
	assertFalse( security.$atLeast(2,"isValidUser") );
}

$once()

This method can help you verify that only ONE mocked method call has been made on the entire mock or a specific mocked method. Useful alias!

Boolean $once([methodname])

Parameters:

  • methodName - The optional method name to assert the number of method calls

Examples:

// let's say we have a service that verifies user credentials
// and if not valid, then tries to check if the user can be inflated from a cookie
// and then verified again
function verifyUser(){
	
	if( isValidUser() ){
		log.info("user is valid, doing valid operations");	
	}	
	
	// check if user cookie exists
	if( isUserCookieValid() ){
		// inflate credentials
		inflateUserFromCookie();
		// Validate them again
		if( NOT isValidUser() ){
			log.error("user from cookie invalid, aborting");
		}	
	}
}

// Now the test
function testVerifyUser(){
	
	security = getMockBox().createMock("model.security").$("isValidUser",false);
	security.storeUserCookie("valid");
	security.verifyUser();
	
	assertTrue( security.$once("isValidUser") );
}

$atMost()

This method can help you verify that at most a maximum number of calls have been made to all mocked methods or a specific mocked method.

Boolean $atLeast(minNumberOfInvocations,[methodname])

Parameters:

  • maxNumberOfInvocations - The max number of calls to assert
  • methodName - The optional method name to assert the number of method calls

Examples:

// let's say we have a service that verifies user credentials
// and if not valid, then tries to check if the user can be inflated from a cookie
// and then verified again
function verifyUser(){
	
	if( isValidUser() ){
		log.info("user is valid, doing valid operations");	
	}	
	
	// check if user cookie exists
	if( isUserCookieValid() ){
		// inflate credentials
		inflateUserFromCookie();
		// Validate them again
		if( NOT isValidUser() ){
			log.error("user from cookie invalid, aborting");
		}	
	}
}

// Now the test
function testVerifyUser(){
	
	security = getMockBox().createMock("model.security").$("isValidUser",false);
	security.storeUserCookie("valid");
	security.verifyUser();
	
	// Asserts that isValidUser() has been called at most 1 times
	assertFalse( security.$atMost(1,"isValidUser") );
}

$callLog()

This method is used to retrieve a structure of method calls that have been made on mocked methods of the mock object. This is extermely useful when you want to assert that a certain method was called with the appropriate arguments. Great for testing method calls that save or update data to some kind of persistent storage. Also great to find out what was the state of the data of a call at certain points in time.

Each mocked method is a key in the structure that contains an array of calls. Each array element can have 0 or more arguments that are traced when methods where called with arguments. If they where made with ordered or named arguments, you will be able to know the difference. We recommend dumping out the structure to check out its composition.

struct $callLog()

Examples:

security = getMockBox().createMock("model.security");
//Call methods on it that perform something, but mock the saveUserState method, it returns void
security.$("saveUserState");

//get the call log for this method
userStateLog = security.$callLog().saveUserState;
assertTrue( arrayLen(userStateLog) eq 0 );

$reset()

This method is a utility method used to clear out all call logging and method counters.

void $reset()

security = getMockBox().createMock("model.security").$("isValidUser", true);
security.isValidUser( mockUser );

// now clear out all call logs and test again
security.$reset();
mockUser.$property("authorized","variables",true);
security.isValidUser( mockUser );

$debug()

This method is used for debugging purposes. If you would like to get a structure of all the mocking internals of an object, just call this method and it will return to you a structure of data that you can dump for debugging purposes.

<cfdump var="#targetObject.$debug()#">

Some Examples

The Collaborator

The premise of a collaborator is that it is an object that collaborates with another object for some purpose. Usually this is a dependency of some sort. Now, you can have a collaborator that is injected into the target object by some form of dependency injection, or it is created by the target object. Either way, you need to be able to mock the behavior of this collaborator object. The latter approach is much harder to test because you are doing a createObject() call. However, you need to put into practice your testable and refactor skills to work, in order to make this more testable. Thanks Marc Esher!

Example:

//hard to mock in a method call
function checkEmail(email){
	var validator = createObject("component","model.util.Validator");
	
	return validator.isEmail(arguments.email);
}

The source above is too hard to test because you have no control over the create object call. Therefore you can have two solutions for this, which actually makes your code more testable.

  1. Have a method return to you an instance of the Validator object.
  2. Have the Validator object be injected via dependency injection into your target object.

Most of the time collaborators can be injected by dependency injection, but sometimes they will not as they are only in use by the target object. So let's do both approaches, starting with dependency injection via annotations: cfproperty.

<cfproperty name="Validator" type="model" instance="scope" />

//or setter injection

<cffunction name="setValidator" access="public" returntype="void" output="false">
	<cfargument name="validator" type="any">
	<cfset instance.validator = arguments.validator>
</cffunction>

Now that we have a annotation injection or a setter injection, let's mock the collaborator into our target object with the mocked method for isValid email.

targetObject = createObject("component","model.TargetObject");
//prepare it for mocking, don't remove any methods
getMockBox().prepareMock(targetObject);

//Create our mock collaborator
mockValidator = getMockBox().createEmptyMock('model.Validator');
//Mock the isEmail valid method
mockValidator.$("isEmail",true);

//Inject it with setter injection
targetObject.setValidator(mockValidator);

//Inject it via mock property
targetObject.$property("validator","instance",mockValidator);

There you go. Now let's refactor our method call:

function checkEmail(email){
	return instance.validator.isEmail(arguments.email);
}
//or 
function checkEmail(email){
	return getValidator().isEmail(arguments.email);
}

As you can see, even our method look cleaner and sharper thanks to our refactoring. However, the most important aspect of our refactoring is that now we can mock the collaborator.

Method Spies

The premise of method spies is that you want to mock the return of method calls inside the target object that we are testing because these methods are helper methods. Also, maybe these helper methods have an access type of private which we cannot mock directly. Here is a typical example in one of my security objects:

function userValidator(rule){
//validate a user
var user = getUserSession();

if( user.isAuthorized() ){
	return true;
}

//Check if single sign on cookie exists and is valid
if( isSSOCookieValid() ){
	authorizeUser(user);
}
else{
	return false;
}
}

Ok, this simple liner security validator throws tons of problems. Why? Well, I have three internal private calls:

  • getUserSession() - supposed to return a nice user object
  • isSSOCookieValid() - checks if a single sign on cookie is set
  • authorizeUser() - Authorize a user in my system

Since I am unit testing this target object's userValidator method, I don't really care about the method calls, I just worry about the behavior that is associated with them because my other tests will go into detail about what they do. However, for my purposes of testing the userValidator method, I just need behavior. Thus, we need to be able to create mocking representations of these methods.

function setup(){
//target object
security = getMockBox().prepareMock( createObject("component","model.Security") );
//create a mock user object
mockUser = getMockBox().createEmptyMock("model.User");
}
function testUserValidator(){
//mock user authorizations according to case calls
mockUser.$("isAuthorized").$results(true,false,false);
security.$("getUserSession", mockUser);

//case 1: authorized user
assertTrue(security.userValidator());

//case 2: unauthorized user with invalid cookie
security.$("isSSOCookieValid",false);
assertFalse( security.userValidator() );

//case 3: unauthorized user with valid cookie
security.$("isSSOCookieValid",true);
//mock the authorizeUser void call
security.$("authorizeUser");
assertTrue( security.userValidator() );
}

As you can see from the example above, I mocked all the necessary data and objects to test all the paths that I could follow in my user validator method. Now we are cooking with MockBox.

Conclusion

Now you have seen the power of the MockBox!, a minimalistic and simplistic approach to mocking and stubbing for ColdFusion applications. So remember that you can use this library outside of your ColdBox applications like any other mocking/stubbing framework, but you can also use it in your ColdBox testing with some added features. Welcome to the world of Mocks!!


ColdBox Books