Blog

Luis Majano

May 17, 2010

Spread the word


Share your thoughts

Once you get an appreciation for the importance of unit testing and integration testing is when we reach a new level in our development careers.  Testing is critical to mission critical applications, and even for our own little projects, where we test that our code should work as expected.  There’s that word again, expected.  Expectations in unit testing is like a nasty hamburger at a soccer match in El Salvador.  They go hand in hand :)

With unit testing, we are always trying to isolate dependencies so we can focus on the meat of our objects and make sure they work as we design them to work without all the overhead that integration testing can give us like doing startup operations, preparations, shutdown, etc.  However, please don’t think that integration testing should not be done, it is essential, and of course, ColdBox rocks at integration testing also.  This article is more of the focus of mocking via MockBox, why do it, how to do it and to demystify it as a Jedi mind trick but a mere mortal event.

I will show you how to test a component that would usually require integration testing using regular MXUnit tests in order to achieve its result.  I have a person service layer that I will lay out for you with its basic operations and dependencies and show you how to test the entire service layer without the need of instantiating the entire gamut of components or having it communicate with the database.  If you are not familiar with MockBox, MockBox is a standalone stubbing and mocking framework for ColdFusion.  You can use it as a standalone framework or it is packaged with the ColdBox Platform downloads.  So make sure you have the appropriate downloads.

Mock Objects and Dependencies

Pretty much any domain logic component has some interaction or coupling with other components in order to enhance it or provide internal services.  It may be a simple domain object with business logic, a CF ORM DAO that returns entities from the database, a plain DAO that returns queries, a helper object, etc.  At the end of the day, once employing more layers into our domain logic, the more complexities and couplings where are creating.  This is normal, the nature of object orientation when we rely on object communications.

Mock objects help us test these dependencies as we do not really need to create them at runtime or even have them exist yet.  We are testing to a pre-defined set of expectations and we want to simulate them so we can test that our isolated component WORKS!  If you are using a dependency injection framework in your applications, then even better, as much of the machinery of getting the objects into the testable components are there already.  If not, don’t worry, MockBox leverages the dynamic nature of ColdFusion, so we can get your mock objects inside of these components no matter where they are!

Code Sample

Below is my person service that provides some useful services to my applications.  This particular service is using dependency injection via WireBox.  WireBox is the dependency injection framework included with the ColdBox Platform and will also be standalone and available soon.

 

/** * My Person Service layer * @singleton */ component{ // Dependencies property name="dao" inject="model:personDAO"; any function init(){ super.init(entityName="Person"); return this; } array function findAll(){ return dao.findAll(); } any public find(required numeric id){ var person = dao.get(id=arguments.id); if( isNull( person ) ){ person = dao.new(); } return person; } boolean function deleteAll(){ try{ dao.deleteByQuery("from persons"); return true; } catch(Any e){ return false; } } boolean function delete(required numeric id){ try{ dao.deleteByID(id=arguments.id); return true; } catch(Any e){ return false; } } }

Creating Mocks

Make sure that you have the MockBox library imported into your MXUnit tests or if you are testing within ColdBox, you are golden, everything is there already by just calling getMockBox(). Below I am showing the standalone version:

import mockbox.system.testing.*; component extends="mxunit.Framework.TestCase"{ function setup(){ mockBox = new MockBox(); // Mock the DAO dao = mockBox.createEmptyMock(className="PersonDAO"); // Create service with mocking capabilities service = mockBox.createMock(className="PersonService"); // Inject dao into service via MockBox model.$property("dao",dao,"variables"); } }

The ColdBox test case version looks like this:

/** * @model PersonService */ component extends="coldbox.system.testing.ModelTestCase"{ function setup(){ // ColdBox creates and mocks the service for you as variables.model // Mock the DAO dao = getMockBox().createEmptyMock(className="PersonDAO"); // Inject dao into service via MockBox model.$property("dao",dao,"variables"); } }

You can see that we use a createEmptyMock() and createMock() methods.  These two methods are similar as they apply mocking capabilities to an object.  However, the empty mock means that we completely remove all of the object’s functionality and we are only interested in its signatures.  That is why the DAO object is built as an empty mock and the service as a normal object but enhanced with mocking capabilities.  Why? To allow the service to be able to use mocking internally.  We also use the $property() method which allows you to inject dependencies into objects into any internal object scope.  In our case, the variables scope.

Very Methods Were Called

Let’s now call the service’s findAll() method and verify that the dao’s call is also made.  We do this via our $() method which allows us to mock ANY method we want and also the $verifyCallCount() method which allows us to verify how many times a method has been called.

 

function testFindAll(){ all = arrayNew(1); arrayAppend(all, new Person(1,"Luis","Majano") ); arrayAppend(all, new Person(2,"John","Tolkien") ); // Mock the dao's find call dao.$(method="findAll",returns=all); // Call the service method to test results = model.findAll(); // Verify our call assertTrue( dao.$verifyCallCount(1,"findAll") ); }

Cool, we now know that it works!

Test Different Results (State Machine)

We can also use the same approach to test for different results from the same mocked method.  We can test for a found collection list or for an empty found collection list.

 

function testEmptyReturn(){ all = arrayNew(1); // Mock the dao's find call dao.$(method="findAll",returns=all); // Call the service method to test results = model.findAll(); // Verify our results assertEquals( 0, arrayLen(results) ); }

We can also test using a state machine of results, or say we want to test the find() method with 1 id that exists but another that does not exist. For this, we concatenate our mock method to another mock method called $results(), which takes in an array of results you would like to return depending on the number of times you call it.  So if you say: $results(1,2), the first call returns 1, the second call returns 2 and a third call would repeat the sequence back to 1 and starts all over again.

function testFindResponses(){ // mock the find methods dao.$("find").$results( new Person(1,"Luis","Majano"), javaCast("null","") ); // Test the first results object p = model.find(1); assertEquals( 1, p.getID() ); // Test the second results object p = model.find(99); assertEquals( "", p.getID() ); }

Mock Data Logging

Another important aspect of MockBox is that it takes snapshots of all the arguments made to a mocked method call so you can later on inspect them or assert to them.  You retrieve them by using the method $callLog(), which returns to you a structure that contains all the mocked methods and the value is an array containing all the arguments.

 

function testCorrectnes(){ // mock the find methods dao.$("find").$results( new Person(1,"Luis","Majano") ); // Test the first results object p = model.find(1); // Test the argument passed to the DAO for the 1st call. assertEquals( 1, dao.$callLog().find[1].id ) }

So in summary, the $callLog() really helps out in telling you for each method call what where the arguments that got sent.  If you are using named arguments, then you just use the name in the structure, if not, they will be positional: $callLog().find[1][1]

Test Exceptions

You can also use the same $() mocking method to tell MockBox to throw a controlled exception when calling the deleteAll() method in my service and then testing the results.  This becomes really nice and easy:

 

function testException(){ // mock the exception dao.$(method="deleteAll",throwsException=true,throwsMessage="Invalid State"); // Test the call results = model.deleteAll(); // Test delete failed. assertEquals( false, results); }

For extra credit, try out the $debug() method, you will be surprised at all of its awesomeness!!

Summary

I am of course, only showcasing a small amount of features of MockBox here, but what I wanted to express in this article is the concept and NEED of mocking when you start to create more complex applications that rely on several objects.  I believe that understanding the importance of unit testing, mocking and integration testing is one of the key assets of a senior ColdFusion developer.  It will sharpen your skills, motivate you to learn, code more consistently and make you stand out in today’s job market.  I know that when I see resumes and I see topics and skills such as integration testing, unit testing, mocking, etc, that really makes me happy and gives me the energy to go after those candidates.

Recent Entries

Hackers demand a ransom to restore data from my ColdFusion web applications!

Hackers demand a ransom to restore data from my ColdFusion web applications!

Hackers demand a ransom to restore data from my ColdFusion web applications!

Unfortunately, we often hear this message from clients who thought it would never happen to them... until it did. Some believed they could delay the expense of Implementing ColdFusion security best practices for one year, while others were tempted to put it off for just a few months. However, in today's rapidly evolving digital landscape, the security of web applications, including ColdFusio...

Cristobal Escobar
Cristobal Escobar
April 16, 2024
Ortus March Newsletter

Ortus March Newsletter

Welcome to Ortus Solutions’ monthly roundup, where we're thrilled to showcase cutting-edge advancements, product updates, and exciting events! Join us as we delve into the latest innovations shaping the future of technology.

Maria Jose Herrera
Maria Jose Herrera
April 01, 2024
Into the Box 2024 Last Early Bird Days!

Into the Box 2024 Last Early Bird Days!

Time is ticking, with less than 60 days remaining until the excitement of Into the Box 2024 unfolds! Don't let this golden opportunity slip away; our exclusive Early Bird Pricing is here for a limited time only, available until March 31st. Why wait? Secure your seat now and take advantage of this steal!

Maria Jose Herrera
Maria Jose Herrera
March 20, 2024