MockBox

last edited byusericonlmajano on 23-Jun-2010

<< Back to Dashboard

Contents

MockBox: The ColdBox Mocking Framework

Covers up to version 1.2

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 do outside of ColdBox applications.

Resources

System Requirements

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

Other Requirements:

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) http://www.netobjectives.com/emergent-design-evolutionary-nature-professional-software-development

When doing unit testing of components, we will come to a point where a single class have can multiple external dependencies; whether they are classes themselves or data. Therefore, we need to be able to mock this data, behavior or components in order to unit test our class exclusively and easily. If not, we would be instantiating and creating entire set of components to just test one single piece of functionality or behavior or even to the extent of creating database records, files, etc. Mock objects can be created by hand, but MockBox takes the pain away by leveraging dynamic techniques so you can Mock dynamically. 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." So what you will be basically leveraging MockBox for is to create objects that represent your dependencies or even data. You can then very easily test the exclusive behavior of components.

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 the 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.

Our Approach & Features

The approach that we take with the 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?

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

Creating MockBox

If you are basing your testing on the ColdBox base test case, then you already have the mock factory created for you. You can just retrieve it by using the getMockBox() method from your unit tests. However, if you are using this outside of the ColdBox base test case, 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(), 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:

createEmptyMock()

Used to create a new mock object with all its method signatures wiped out, this is a utility method.

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

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.

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

Parameters:

As you can see from the arguments above, you can choose whether you want the mock factory to create the mock object for you or you can pass in an already instantiated object to the factory. If you pass an already instantiated object, the factory will decorate it with the mocking methods so you can target specific methods or properties to mock on that object. If not, the factory will create and return to you a mocked object. The method prepareMock() basically calls the createMock() method with the object parameters.

The clearMethods argument is also important. By default, the argument is set to false. This means that the factory will create your mock object, decorate it and return it to you intact. All methods and code are intact and it should function as normal, but with extra mocking capabilities. You can then target what to mock a-la-carte. However, there comes a time, when you want to completely eliminate all functionality in the mock and just retain its signature. You can do this by setting the clearMethods argument to true. This will wipe off all the methods in the mock object and it will be your responsibility to mock the methods and properties, if not an exception will be thrown.

The last argument is callLogging, which by default is turned to true. If call logging is turned on, then the mock object will keep track of all method calls to mocked methods. It will store them in a sequential array with all the arguments the method was called with. 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.

Examples:

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

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

import coldbox.system.testing.*;

component extends=”mxunit.framework.TestCase” {
  function setup(){
	mockBox = new MockBox();
    
    //Create the User Service to test, do not remove methods, just prepare for mocking also.
	userService = mockBox.createMock(className="model.UserService");

	// Mock the session facade, I am using the coldbox one, it can be any cfc
	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(transfer,mockSession,mockDAO);	
  }
}

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">

</cffunction>

</cfcomponent>

Creating a Stub Object

In order to create a stub object you need to use the method: createStub.

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

Parameters:

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.

Mocking Methods

Once you have created a mock object, you can use it like the normal object as it will respond exactly as it should. 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, also note that apart from the method name, you also have the MockBox method alias that you can also use to call them:

Method Name Method Alias Description
mockMethod() $() Mock a method.
mockProperty() $property() Mock a property in the object.
mockResults() $results() Mock 1 or more results of a mock method call.
mockArgs() $args() Mock 1 or more arguments in sequential order. Must be called concatenated to a mockMethod call and must be followed by a concatenated mockResults call.
mockMethodCallCount() $count() Ask for a method's call counter.
mockVerifyCallCount() $verifyCallCount() Assert how many calls have been made to the mock or a specific mock method
mockCallLog() $callLog() Retrieve the method call logger structure.
mockDebug() $debug() Retrieve the mocking debugging information about an object.


Note: Please note that all methods have their shorthand aliases starting with the $ sign. This is done to achieve a more visual impact on the developer and also because of the great syntax notation it provides.

mockMethod or $

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 mockMethod(string method, [any returns], boolean preserveReturnType='true', [boolean throwException='false'], [string throwType=], [string throwDetail=], [string throwMessage=], [boolean callLogging='false'])

Parameters:

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()#">

mockResults or $results

This method can only be used in conjunction with mockMethod/$ as a chained call:

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

Why chained? Well, because it follows suit that you are talking about the results for the currently called mocked method. 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.$(method="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(classNameToMock="model.User");
mockUser.$("isAuthorized").$results(false,true);

mockArgs or $args

This method is used to tell mock box 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().createMock(className="coldbox.system.beans.ConfigBean",clearMethods=true);
//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.

Important Note: The argument values have to be EXACTLY in the same case as they are called in the code being tested. If not, they will not match, so make sure the argument values are the SAME as how they are called.

mockMethodCallCount or $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 been mocked, then it will return a -1.

numeric mockMethodCallCount([string methodName])

Parameters:

mockUser = getMockBox().createMock(classNameToMock="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() );

mockProperty or $property

This method is used in order to mock an internal/external 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. How do we mock this? Very easily by using the mockProperty method on the target object.

void mockProperty(any obj, string propertyName, [string propertyScope='variables'], any mockObject)

Parameters:

userService = createObject("component","model.UserService");
//decorate our user service with mocking capabilities, just to show a different approach
getMockBox().prepareMock(userService);

//create a mock dao
mockDAO=getMockBox().createMock(className="model.UserDAO",clearMethods=true,callLogging=true);
mockDAO.$(method="getUsers",returns=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!

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

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

mockCallLog or $callLog

This method is used to retrieve the structure of method calls that have been made on mocked methods of a certain object. This is extermely useful when you want to assert that a certain method was called with 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 data state of a call at certain points in time. Basically, by adding call logging capabilities to an object you can get a snapshot of all method calls with their arguments.

struct mockCallLog()

Examples:

security = getMockBox().createMock(className="model.security",callLogging=true);
//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;

mockVerifyCallCount or $verifyCallCount

This method is used to assert how many times a mocked method has been called or ANY mocked method has been called. This is a great way for you to test the times a methods is called within a test case.

Boolean mockVerifyCallCount(numeric count, [methodname])

Parameters:

Examples:

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

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

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

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

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

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

mockDebug or $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().createMock(className='model.Validator',clearMethods=true);
//Mock the isEmail valid method
mockValidator.$(method="isEmail",returns=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:

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 = createObject("component","model.Security");
getMockBox().prepareMock(security);
//create a mock user object
mockUser = getMockBox().createMock(className="model.User",clearMethods=true);
}
function testUserValidator(){
//mock user authorizations according to case calls
mockUser.$("isAuthorized").$results(true,false,false);

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

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

//case 3: unauthorized user with valid cookie
security.$(method="isSSOCookieValid",results=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.

Mocking Argument Collections

Most of us make calls to methods via the argumentCollection. This is great but presents a caveat when mocking arguments that are passed via argumentCollection. This is because ColdFusion passed the argument collection as a set of name value pairs and most of the time upper cases the keys. So if we have a struct like with the following keys: [name][age], ColdFusion will pass them as: [NAME],[AGE]. This is how ColdFusion does it, so don't ask me why!

Anyways, if you use the mocking methods and use the $args() method without passing named parameters, your mock results will NOT be found because of the mismatch of the arguments. So always remember that if you are using argument collection, then you must use named parameters and they must be upper case. In the next releases we will be working on alternatives to make this easier.

service.$("getUsers").$args(NAME="Luis",AGE="32").$results([mockUser]);

Conclusion

Now you have seen the power of the MockBox!, a very 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!!

 
Download in other Formats:
markup Markup | pdf PDF | swf SWF | html HTML | word Word

comments Comments (3)

dchesterman@certain.com's Gravatar

Dustin Chesterman said

at 01:40:19 PM 22-Jun-2010

Can you provide an example of mocking a method that is called with argumentCollection in the tested method? All of the examples here demonstrate literal arguments. Often this is not the case.
lmajano@gmail.com's Gravatar

Luis Majano said

at 10:26:22 AM 23-Jun-2010

Hi dustin, you have to be careful with argument collections as they pass arguments by named parameters and not positional. So if you mock them by position they will not work. I am adding a note on this.
lmajano@gmail.com's Gravatar

Luis Majano said

at 12:10:34 PM 11-Aug-2010

This has been fixed on the current release, you can now call the methods in any which order and it will mock the results correctly.