ColdFusion unit testing and using MockBox

I wanted to put this brief post together as there has been a few questions about using a framework that does Mocks in ColdFusion.

Today I am going to explain how to get a basic one up and running in ColdFusion 9, but you can change the syntax from script to tags and it work just as well.

</more>

So before I get into MockBox, why did I chose MockBox?

When looking at what is around that supports Mocking in ColdFusion I first began to look at mxUnits way of doing it, and wasn't a fan of it, but granted there was very little information on how to effectively use it when I was looking. That didn't mean that the mxUnit community were not helpful, as they were able to answer all questions I put forward.

Then I heard about MockBox and began using it since it was released, during the alpha cycle. MockBox is based on the ColdMock that is out there but with some more bells and whistles that come along with it. I have never used ColdMock and only know of its existence, so I will not be able to answer any questions on the differences on the two frameworks.

So how do I get going with MockBox?

Its very easy to install, and there is no configuration needed to have it up and running. The only requirement, that you need to do which is not really explained in the download or the website. Is that you need to create a directory called ColdBox and install the files underneath that, other than its all ready to go.

 

Creating our Unit Test

I am going to create a very simple object that will create a user, and show how to implement our database calls with Mock objects.

To do this we need to create 3 objects, 2 of these objects are real and one is our Unit Test. I usually follow a different structure than this, but I am going to create 2 directories. The first is our components directory, and I'll call this directory com, and underneath that I will create 2 more dataObjects and services. Remember this is just a quick post, and I do not follow this convention, so please adapt this to your scenario.

With these directories created I am going to create a user component in the dataObjects directory that is going to be a shell at this stage, and for this tutorial.

user.cfc

component {
}

 

I am now going to create another component in the services directory that is also going to be empty at this stage and it has the same code as above for now.

Now when I do my Unit Testing I always follow the same structure as the component hierarchy that I am unit testing, so with that in mind I am going to create the directories com->services in the UnitTests directory that will be created in the root of the application, well append the word test to our component so it becomes userServiceTest.cfc.

And the code is going to look like this for now.

component output='false' extends='mxunit.framework.TestCase' {
   //------------------------------------------------------------------------------------------------
   //   setup
   //------------------------------------------------------------------------------------------------
   public void function setUp() output='false' {
   }
   //------------------------------------------------------------------------------------------------
   //   tear down
   //------------------------------------------------------------------------------------------------
   public void function tearDown() output='false' {
   }
   //------------------------------------------------------------------------------------------------
   //------------------------------------------------------------------------------------------------
   public void function createUser() output='false' {
      fail('not implemented as yet');
   }
}

You will also notice that we have added a function in here that will be our createUser test, and that if you run this test it will display the message as described in the fail call.

 

Writing our createUser test

Normally when writing a Unit Test a lot of refactoring would be happening here, however today I am going to give you the final result so as not to bore you with the tedious task of the refactoring that would normally take place.

In our userService.cfc we are now going to write the following code inside it.

public function init() {
    instance.userObject = new com.dataObjects.user();
   }
   public function getUserObject() {
    return instance.userObject;
   }
   public function setUserObject(required any userObject) {
    instance.userObject = arguments.userObject;
   }

Remember that this is ColdFusion 9 script, and that the new operand will need to be replaced by a normal CreateObject.

A very quick explanation here, after refactoring this code. We needed to use the methods get/set userObject so that we could create a Mock Object, and would still run when used in our application.

 

Mocking our object for use

In our Unit Test code we are going to place the following code in our setup method.

mockBox = createObject("component","coldbox.system.testing.MockBox").init();
   mockUser = mockBox.createMock(className='com.dataObjects.user', clearMethods=true);
   mockUser.$(method='createUser').$results(true, false);
   object = createObject('component','com.services.userService');
   object.setUserObject(mockUser);
   super.setup();

This is where all the magic will now happen for our Unit Tests, and it is fairly straight forward. But I will give a very quick run down on how it is being used.

We are first of all creating a MockObject, and we are then telling this object that it is going to be based on the user.cfc and that we are going to clear all methods in that object. In this example it is not important whether we clear them or not, but if there are other methods in that Mock that are required you will need to make this false. And you will also need to make sure that any other method that will need to be mocked is also setup to not call the database.

Hopefully that makes sense, but please ask if you do require some more information.

Now the next step in the above process is that we are then creating the method createUser, and that any results that are going to be returned will be returned. In this example I have done True and False, the reason here is that this is what the original method would return True for successful and False if it is not.

The last part of this is then injecting the Mock Object into our userService, so that the Mock can take place.

 

Finishing our Unit Test

Now if you try to run the unit test it will still fail, because we have told it too do so. So lets finish that section of code up now.

What we need to do know is write the test to handle the situations that we will expect, so for lets add the createUser test code as such.

var userData = {
      username='user', password='password'
   };
   result = object.createUser(userData);
   assertTrue( result, 'Failed to create a user');
   userData['username'] = 'anotheruser';
   result = object.createUser(userData);
   assertFalse( result, 'Failed to handle not creating a user');

In the above code we setup a struct to hold our data and pass that into the actual createUser, this is not the only way to do this and you will need to make the changes to suit your needs.

I will also need to explain this in the first call to create the user, the Mock object will return true so it should pass. But if it doesn't se set the message to tell us that it has failed to create the user. The second call to createUser is going to return false, so we then check that this is the case too. But in the off chance it fails we give an appropriate message to help us understand why.

But how do I know that they are going to return true and false in that order?

If we go and look at our setup code in the Unit Test we see this.

mockUser.$(method='createUser').$results(true, false);

This code tells us that the first call will be true, the second will be false and the 3rd will also be true. But wait a minute there is only 2 values, so how it works is that it cycles back to the beginning again and returns the equivalent result based on the the times it was called.

Now this test will not run, and we need to make one last change to our actual userService and to do that we need to now add the following code to the createUser.

public function createUser(required struct userData) {
    return getUserObject().createUser(arguments.userData);;
   }

And the last thing to do is run our test, and watch it pass.

In Conclusion

This was put together as an example to explain the benefits, and to demonstrate how to create a Mock object for database calls and any other object that needs to be mocked. In a real world example I would be more inclined to create a parent object that would have the setup and the tearDown so that we would be reusing our code.

The downside to this and I have demonstrated it here, is that the actual database call is not made, however we don't test this and it can still be left open to error's when we do mocking in this manner. So please be aware as I have fallen into this trap before, when the tests all pass the actual database call to createUser did fail on a project I was working on.

Anyway I hope you find this informative.