A First Look at MightyMock

November 27, 2009

MightyMock is a mock framework to help you unit test your applications. I'm using it with MXunit, but you should be able to use it with any Unit Testing framework. So why do you need it?

Well, imagine that you're building an application that has a SecurityService. In your SecurityService you want a method which checks that the current user has a particular permission. OK, let's have a look at what the SecurityService might look like (as with most sample code, this is a bit contrived!)


<cfcomponent output="false">

<!---
create instance struct outside init as dependancy injection frameworks
often call setters before calling init method
--->

<cfset instance = {}>

<cffunction name="init" access="public" output="false" returntype="SecurityService">
<cfscript>
return this;
</cfscript>
</cffunction>

<cffunction name="hasCurrentUserGotPermission" access="public" output="false" returntype="boolean">
<cfargument name="permission" type="string" required="true">

<!--- get the user id --->
<cfset var userid = instance.SessionFacade.getKey("userid")>
<!--- get a User object from the UserGateway --->
<cfset var User = instance.UserGateway.getUserByID(userid)>
<!--- get an array of user's permissions --->
<cfset var userPermissions = User.getPermissions()>
<!--- user is not authorised by default --->
<cfset var authorised = false>
<cfset var index = "">

<cfloop array="#userPermissions#" index="index">
<cfif arguments.permission eq index>
<cfset authorised = true>
<cfbreak>
</cfif>
</cfloop>

<cfreturn authorised>

</cffunction>

<!--- setters to inject dependancies --->
<cffunction name="setSessionFacade" access="public" output="false" returntype="void">
<cfargument name="SessionFacade" type="any" required="true">
<cfset instance.SessionFacade = arguments.SessionFacade>
</cffunction>

<cffunction name="setUserGateway" access="public" output="false" returntype="void">
<cfargument name="UserGateway" type="any" required="true">
<cfset instance.UserGateway = arguments.UserGateway>
</cffunction>

</cfcomponent>

Nothing particuarly unusual going on here, but how do we test it in isolation? When we unit test an object we really just want to test the actual object independantly of any other objects. In this example, the SecurityService depends on a UserGateway and a SessionFacade, also, the UserGateway is going to return a User object. That's three other objects that we need just to test our SecurityService, and the UserGateway calls a database! This is where mock and stub objects come into play.

What a mock framework does is create "fake" objects that stand in for our real objects. In fact, I can test my SecurityService.cfc before I even write any other cfcs and without needing a database!

To use MightyMock we need to download it (I downloaded my copy straight from github) and extract it into the webroot. You'll also need to have MXUnit installed to run my unit test code.

Once you've done that, create the SecurityService.cfc (code above) and the SecurityServiceTest.cfc (code below) in the same directory.


<cfcomponent extends="mxunit.framework.TestCase" output="false" hint="I test the SecurityService">
<!---
to run this test in a browser append ?method=runtestremote to the cfc path
--->


<cffunction name="testUserCanNotDelete">
<cfscript>
// this is the object we want to test
SecurityService = CreateObject('component','SecurityService').init();

/*
our securityservice depends on a SessionFacade and a UserGateway, but
we just want to test the Service independantly, especially as we haven't
even created a SessionFacade or a UserGateway yet! So create mocks.
*/

userid = 123456;

$SessionFacade = CreateObject('component','mightymock.MightyMock').init('SessionFacade');
$UserGateway = CreateObject('component','mightymock.MightyMock').init('UserGateway');
$User = CreateObject('component','mightymock.MightyMock').init('User');

// mock SessionFacade should return userid for getKey('UserID')
$SessionFacade.getKey('UserID').returns(userid);

// give our mock User methods
permissions = ['create','edit'];
$User.getPermissions().returns(permissions);

/*
we want the UserGateway getUserByID method to return our mocked
user object if any numeric value is passed in
*/

$UserGateway.getUserByID('{numeric}').returns($User);

debug($UserGateway.getUserByID(userid).getPermissions());

// inject dependancies
SecurityService.setSessionFacade($SessionFacade);
SecurityService.setUserGateway($UserGateway);

result = SecurityService.hasCurrentUserGotPermission('delete');
assertFalse(result, "The user should not be able to delete");
</cfscript>
</cffunction>

<cffunction name="testUserCanDelete">
<cfscript>
// this is the object we want to test
SecurityService = CreateObject('component','SecurityService').init();

/*
our securityservice depends on a SessionFacade and a UserGateway, but
we just want to test the Service independantly, especially as we haven't
even created a SessionFacade or a UserGateway yet! So create mocks.
*/

userid = 654321;

$SessionFacade = CreateObject('component','mightymock.MightyMock').init('SessionFacade');
$UserGateway = CreateObject('component','mightymock.MightyMock').init('UserGateway');
$User = CreateObject('component','mightymock.MightyMock').init('User');

// mock SessionFacade should return the userid for getKey('UserID')
$SessionFacade.getKey('UserID').returns(userid);

// give our mock User methods
permissions = ['create','edit','delete','restore'];
$User.getPermissions().returns(permissions);

/*
we want the UserGateway getUserByID method to return our mocked
user object if any numeric value is passed in
*/

$UserGateway.getUserByID('{numeric}').returns($User);

debug($UserGateway.getUserByID(userid).getPermissions());

// inject dependancies
SecurityService.setSessionFacade($SessionFacade);
SecurityService.setUserGateway($UserGateway);

result = SecurityService.hasCurrentUserGotPermission('delete');
assertTrue(result, "The user should be able to delete");
</cfscript>
</cffunction>

</cfcomponent>

Then fire it up in a web browser with the url:

http://myservername/mydirectory/SecurityServiceTest.cfc?method=runtestremote

There you go, you've successfully tested that your SecurityService is working correctly without having to create the other cfcs or enter dummy data into your database. Pretty cool huh?

Now I know what you're thinking: "that's a lot of duplicate code for a simple test!". Well, yes it is, thankfully MXUnit has a setup method which is called when each test is run so let's take a detour from the point of this post and refactor...


<cfcomponent extends="mxunit.framework.TestCase" output="false" hint="I test the SecurityService">
<!---
to run this test in a browser append ?method=runtestremote to the cfc path
--->

<cffunction name="setup" hint="I run before each test">
<cfscript>
// this is the object we want to test
SecurityService = CreateObject('component','SecurityService').init();

/*
our securityservice depends on a SessionFacade and a UserGateway, but
we just want to test the Service independantly, especially as we haven't
even created a SessionFacade or a UserGateway yet! So create mock versions
of dependancies. I've prefixed them with a $ but that is up to you.
Passing the object type to the init argument is optional if you are not
using type checking.
*/

$SessionFacade = CreateObject('component','mightymock.MightyMock').init('SessionFacade');
$UserGateway = CreateObject('component','mightymock.MightyMock').init('UserGateway');
$User = CreateObject('component','mightymock.MightyMock').init('User');

userid = 123456;

// mock SessionFacade should return userid for getKey('UserID')
$SessionFacade.getKey('UserID').returns(userid);

/*
we want the UserGateway getUserByID method to return our mocked
user object if any numeric value is passed in
*/

$UserGateway.getUserByID('{numeric}').returns($User);

// inject dependancies
SecurityService.setSessionFacade($SessionFacade);
SecurityService.setUserGateway($UserGateway);
</cfscript>
</cffunction>

<cffunction name="testUserCanNotDelete">
<cfscript>
// give our mock User permissions
permissions = ['create','edit'];
$User.getPermissions().returns(permissions);

// you don't need to debug but it is quite handy
debug($UserGateway.getUserByID(userid).getPermissions());

// test the method
result = SecurityService.hasCurrentUserGotPermission('delete');
assertFalse(result, "The user should not be able to delete");
</cfscript>
</cffunction>

<cffunction name="testUserCanDelete">
<cfscript>
// give our mock User permissions
permissions = ['create','edit','delete','restore'];
$User.getPermissions().returns(permissions);

// you don't need to debug but it is quite handy
debug($UserGateway.getUserByID(userid).getPermissions());

// test the method
result = SecurityService.hasCurrentUserGotPermission('delete');
assertTrue(result, "The user should be able to delete");
</cfscript>
</cffunction>

</cfcomponent>

Once again I must thank Mr Bob Silverberg for his input.

 

 


4 comments

  1. In your opinion, how would you compare MightyMock to ColdMock? Is there something one has that the other doesn't?

    Thanks.

    Comment by Henry Ho – November 27, 2009
  2. @Henry, ColdMock has been around for quite a while whereas MightyMock hasn't been released yet. Check out the docs to see what else MightMock can do:
    github.com/bobsilverberg/MightyMock/blob/master/MightyMockDoc.pdf

    Comment by John Whish – November 30, 2009
  3. Nice post, John! I've been interested in MightyMock for a while and this is a great starting point.

    Comment by Paul Marcotte – December 02, 2009
  4. Hey Paul! Thanks. It's nice to know that I've posted something useful for you after all the stuff I've learnt from your blog and digging through Metro :)

    Comment by John Whish – December 03, 2009

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.