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.

Add Your Comment

(1)

May 24, 2010 16:57:58 UTC

by Vladimir Ugryumov

Great post, Luis! MockBox is super handy indeed, and much more approachable than it may seem at first. Examples like these are the best in demonstrating that. Especially with such vivid soccer analogies (LMAO) :D Hopefully the post about good strategy of integration testing is coming up next! ;)

Recent Entries

ColdBox 7.2.0 Released

ColdBox 7.2.0 Released

ColdBox, a widely used development platform for ColdFusion (CFML), has unveiled version 7.2. Packed with compelling new features, bug fixes, and enhancements, this release is designed to empower developers by boosting productivity, refining scheduled task capabilities, and enhancing the overall reliability and efficiency of application development. This article will delve into the key highlights of ColdBox 7.2 and elucidate how these advancements can positively impact developers in their daily coding endeavors.

Luis Majano
Luis Majano
November 20, 2023
Into the Box 2023 Series on CFCast

Into the Box 2023 Series on CFCast

Excitement is in the air as we unleash the highly anticipated ITB 2023 series exclusively for our valued CFCast subscribers – and the best part? It's FREE for CFCast members! Now is the perfect time if you haven't joined the CFCast community yet. Plus, we've got an incredible End-of-Year deal that's too good to miss

Maria Jose Herrera
Maria Jose Herrera
November 20, 2023
Ortus Deals are Finally Here!

Ortus Deals are Finally Here!

The much-anticipated Ortus End-of-the-Year Sale has arrived, and it's time to elevate your development experience! Whether you're a seasoned developer, a tech enthusiast, or someone on the lookout for top-notch projects, Ortus has something special in store for you. Brace yourself for incredible discounts across a wide array of products and services, including Ortus annual events, books, cutting-edge services, and more.

Maria Jose Herrera
Maria Jose Herrera
November 15, 2023