Unit Testing Handlers Guide

Introduction

One of the best things you can do when you develop software applications is TEST!! I know nobody likes it, but hey, you need to do it. With the advocation of frameworks today, you get all this great tools to build your software applications, but how do you test your framework code. As far as I know, ColdBox has revolutionized this, since now you can unit test your event handlers with ease. The new version of 2.0.1 includes an Application Template with unit tests already created for you, so no more excuses. Version 2.0.2 introduces a base unit test component included in the ColdBox's extra directory and unit test suites.

Resources

What is Unit Testing?

In computer programming, unit testing is a procedure used to validate that individual units of source code are working properly. A unit is the smallest testable part of an application. In procedural programming a unit may be an individual program, function, procedure etc, while in object-oriented programming, the smallest unit is always a Class; which may be a base/super class, abstract class or derived/child class. Units are distinguished from modules in that modules are typically made up of units.

Wikipedia

ColdBox is ahead in the testing field of ANY Framework and provides a way to unit test your event handlers by using either cfcUnit or cfUnit. This is really great for development, since you can test that all your event handlers are performing as they should.

How to set it up?

In this guide I will use the included code of the Application Template included in the ColdBox bundle as an example. You can place your tests wherever you want. The basic skeleton of the tests are the following:

|ApplicationTemplate
|--+handlers
    |--+ General.cfc
    |--+ Main.cfc
    |--+ tests
       |--+ AllTests.cfc (Test Suite)
       |--+ cases
          |--+ GeneralTest.cfc
          |--+ MainTest.cfc
        

I have created a base test case cfc that you will need to extend in order to be able to unit test your handler, this cfc is called baseTest and can be found in the following directory: coldbox/system/extras/baseTest.cfc'. So the first step in this guide is to gather the resources:

  1. I have an event handler named General.cfc that has two events: dspHello and doSomething.
  2. I have a test case cfc that extends my coldbox.system.extras.baseTest and has two test methods on it testdspHello, testdoSomething and a setup method where you will place the application's mapping and configuration file location.

The Handler To Test

Below you will see the source of the General.cfc:

<cfcomponent name="General" extends="coldbox.system.eventhandler" output="false">

<cffunction name="dspHello" access="public" returntype="void" output="false">
        <cfargument name="Event" type="coldbox.system.beans.requestContext">
        <!--- Do Your Logic Here to prepare a view --->
        <cfset Event.setValue("welcomeMessage","Welcome to ColdBox!")>  
        <!--- Set the View To Display, after Logic --->
        <cfset Event.setView("vwHello")>
</cffunction>
        
<cffunction name="doSomething" access="public" returntype="void" output="false">
        <cfargument name="Event" type="coldbox.system.beans.requestContext">
        <!--- Do Your Logic Here, call to models, etc.--->

        <!--- Set the next event to run, after Logic, this relocates the browser--->
        <cfset setNextEvent("General.dspSomething")>
</cffunction>

</cfcomponent>

Very simple stuff on the handler, just setting a value in the request collection and setting a view to render. The doSomething method has a relocation in the code via the setnextevent method, so this will fail? Not anymore, since version 2.0.2 a test controller is created for you and you can now test event relocation. So whenever a setnextevent is fired, the test controller will just create a setnextevent variable in the request collection, indicating that a relocation to another event was requested and the value of the relocation is saved. So now to the test case...

IMPORTANT Any relocation produced by the framework via the setNextEvent & setNextRoute methods will produce two variables in the request collection for you to verify relocations. So the variables created will be: setNextEvent & setNextRoute

Notes on using setnextEvent and setnextroute

The test controller for unit testing will intercept any relocations via setnextevent and setnextroute and place at most two variables in the request collection in order for you to assert. The following are the variables that this method will set for you:

Variable Description
setnextevent The value of the event + query string for you to assert. If no query string is supplied, then only the event will be here.
setnextroute The value of the route you want to relocate to.
persistKeys A list of the persist keys that you sent in, in order for you to assert.

The Base Test Case

This cfc is used in order to correctly setup your test cases and to configure ColdBox for Unit Testing. This cfc can be found in the extras directory of your ColdBox distribution folder: coldbox.system.extras.baseTest

This setup differs from version 2.0.1 as you now only modify two variables in your actual test case and not in the base test case. The base test is now static and below are the methods that you will inherit:

Method Description
setup() The setup method where the coldbox controller and virtual application gets created and configured.
getAppMapping() Getter for the application's mapping
setAppMapping() Setter for the application's mapping
getConfigMapping() Getter for the config.xml location
setConfigMapping() Setter for the config.xml location
getcontroller() Getter for the ColdBox controller.
getRequestContext() Getter for the current request collection object (event)
setupRequest() Set's up a new request for execution, called internally only
announceInterception() Announce an interception state with an intercept data
execute() Execute an event to test
dump() and abort() CF Facades

The methods that you will most likely use in your unit tests are: setup(),setAppMapping(),setConfigMapping(), announceInterception() and execute().

The Base test is a cfcUnit test

The base test case is based off cfcUnit, so if you are using CFUnit, then change the extends attribute of the component to net.sourceforge.cfunit.framework.TestCase and you are ready to roll with CFUnit.

So what do I need to know about this base test?

Well, that all your test cases need to extend this component. So you need to know what it does and how it does it, for you to understand how your test cases will work. This component basically sets up a virtual application for you to use without rendering anything.

The test case: GeneralTest.cfc

So what do I modify and what do I do? You will need to modify the setup method created for you in the application template or in the generated application. Basically you need to set up the Application's Mapping, so it can find the handlers, etc. and the location of the config.xml file to use. So you can even create a config.xml file for unit testing, such as coldbox_testing.xml.

<cffunction name="setUp" returntype="void" access="private" output="false">
  <cfscript>
  //Setup ColdBox Mappings For this Test
  setAppMapping("/applications/coldbox/ApplicationTemplate");
  setConfigMapping(ExpandPath(instance.AppMapping & "/config/config.xml.cfm"));
        
  //Call the super setup method to setup the app.
  super.setup();

  //EXECUTE THE APPLICATION START HANDLER: UNCOMMENT IF NEEDED AND FILL IT OUT.
  //getController().runEvent("main.onAppInit");

  //EXECUTE THE ON REQUEST START HANDLER: UNCOMMENT IF NEEDED AND FILL IT OUT
  //getController().runEvent("main.onRequestStart");
  </cfscript>
</cffunction>

The Functions To Call

setAppMapping() The application mapping relative from the web root or via CF Mappings
setConfigMapping The location of your application's ColdBox configuration file. (You can have one explicitly set for Unit Testing: config_tests.xml.cfm)
IMPORTANT NOTE: If you will be testing an application that resides in the root of your web server, then the path is basically the root /. If you set the application mapping to / the tests, they will fail. You will need to change the front slash to a back slash in order for the tests to work from the web root.
//Setup ColdBox Mapping For Web Root Tests
setAppMapping("\");

After you have set up these two variables you are ready to call the extended base case's setup method to create the controller and prepare for testing. You do this by calling the super.setup() method. After that method gets executed, you are ready for testing.

Optional Setup: Application Start and Request Start Handler

  • Execute the Application Start handler if needed. You will need to fill out the name of the Application Start Handler to be executed and uncomment it.
  • Execute the Request Start Handler if needed. You will need to fill out the name of the Request Start Handler to be executed and uncomment it.


You can see below the source code for the full test case:

<cfcomponent name="GeneralTest" extends="baseTest" output="false">

<cffunction name="setUp" returntype="void" access="private" output="false">
  <cfscript>
  //Setup ColdBox Mappings For this Test
  setAppMapping("/applications/coldbox/ApplicationTemplate");
  setConfigMapping(ExpandPath(instance.AppMapping & "/config/config.xml.cfm"));
        
  //Call the super setup method to setup the app.
  super.setup();

  //EXECUTE THE APPLICATION START HANDLER: UNCOMMENT IF NEEDED AND FILL IT OUT.
  //getController().runEvent("main.onAppInit");

  //EXECUTE THE ON REQUEST START HANDLER: UNCOMMENT IF NEEDED AND FILL IT OUT
  //getController().runEvent("main.onRequestStart");
  </cfscript>
</cffunction>

<cffunction name="testdspHello" access="public" returntype="void" output="false">
  <cfscript>
  var event = "";
        
  //Place any variables on the form or URL scope to test the handler.
  //FORM.name = "luis"
  event = execute("General.dspHello");
                        
  //Do your asserts below
  assertEqualsString("Welcome to ColdBox!", event.getValue("welcomeMessage",""), "Failed to assert welcome message");
                        
  </cfscript>
</cffunction>

<cffunction name="testdoSomething" access="public" returntype="void" output="false">
  <cfscript>
  var event = "";
                
  //Place any variables on the form or URL scope to test the handler.
  //FORM.name = "luis"
  event = execute("general.doSomething");
                        
  //Do your asserts below for setnextevent you can test for a setnextevent boolean flag
  assertEqualsString("general.dspHome", event.getValue("setnextevent",""), "Relocation Test");
                        
  </cfscript>
</cffunction>

</cfcomponent>

Once the unit test framework creates this test case, it will execute the setUp and prepare the ColdBox instance for execution. Then we can concentrate on the testdspHello method. Please note that what you are simulating/testing here is actual requests to your application and therefore those request need data. So how do I submit something or send something via the url, well just use the FORM or URL scopes. That easy. So if I wanted to set two variables before I execute the handler, which the handler is expecting, I can do the following:

form.fname = "luis";
form.lname = "majano";
url.mytest = "value";

This prepares the form or url if needed that will be grabbed by the fake controller and create the request collection out of them. The next step you see is the execute() method and the name of the event to test: General.dspHello. This method will take your FORM/URL variables, create them in the request context, execute the handler and come back for your testing with the event object (coldbox.system.beans.requestContext). You can then do any type of asserts on the request collection if needed. So in summary, the execute method, simulates a full browser hit to an event and then you can test if the event did what is was supposed to do. You can even test if the handler relocated by asserting for the setnextevent flag.

Important Caveats with Relative Paths

When doing unit testing of applications you will find out that sometimes it will fail because it cannot find some of the relative file declarations you have done in your coldbox.xml.cfm. This happens because the unit testing framework is the one being executed and ColdBox is just simulating your application. So the best practice is to have a coldbox.xml for unit testing that is separate and has some absolute mappings. Some of the settings to watch out for are the following:

  • IOCDefinitionFile: If you have for example: config/Coldspring.xml, change it to the full path, /MyApp/config/Coldspring.xml
  • ColdBoxLogsLocation
  • UDFLibraryFile

ColdBox Test Suites

So now that you completed your incredible unit test, you can also create testing suites. The Application Template comes with a pre-coded testing suite to cover the two test cases: generalTest.cfc and mainTest.cfc. So below you can see the code for the test suite:

<cfcomponent displayname="AllTests" output="false" hint="Test suite for test cases.">  
        
        <cffunction name="suite" returntype="org.cfcunit.framework.Test" access="public" output="false">  
                <cfset var suite = CreateObject("component", "org.cfcunit.framework.TestSuite").init("Test Suite")>  
                
                <!--- Add the test cases --->
                <cfset suite.addTestSuite(CreateObject("component", "cases.generalTest"))>
                <cfset suite.addTestSuite(CreateObject("component", "cases.mainTest"))>
                
                <cfreturn suite/>  
        </cffunction> 

</cfcomponent>

Again, I am basing my test suite on cfcUnit, you can change the appropriate suite object to your testing framework as needed. All this does, is add the two test cases we created to the suite. We can then test all of them at once via cfcUnit.

Eclipse Integration via cfcUnit CFEclipse Facade

Thanks to Sean Corfield and Mark Drew's CFEclipse Project, you can also test your unit tests via CFEclipse. I am assuming you have Eclipse, CFEclipse and cfcUnit installed in your machine. All you need to do is the following:

Step 1: CFEclipse cfUnit Plugin

Make sure you install the cfUnit Plugin from the CFEclipse's distribution site. I will not delve here, there are plenty of tutorials on how to do this. For more information visit http://www.cfeclipse.org/index.cfm?event=page&page=download

Step 2: Download Sean's CFEclipse Facade

To install, first read Sean's Post at http://corfield.org/blog/index.cfm/do/blog.entry/entry/cfcUnit_Facade_for_CFUnit_Plugin, all you do is place the cfc in a web accessible location. In my case, I just place it inside of my cfcUnit directory.

Step 3: Configure the CFUnit plugin via Eclipse

To do this, go to Window > Preferences. Once the preference panel open, expand the CFEclipse option on the left and click on the CFUnit option. You will see the following screen, then just fill out the URL of the facade location:

Step 4: Open the CFUnit View

Click on the CFUnit icon or goto Window > Show View > Other... or via the view icon if you have one.

You can then see a listing of all the available views, look for the CFUnit view in the CFML folder:

You then press ok, and you will get the main CFUnit screen:

Step 5: Type the path of the test case or suite test

You can then type the path of the test case or suite to test, in my case, I type the path of my test suite: AllTests? and then click on the execute button that looks like a play button. I can then see my results:

Conclusion

As you can see, ColdBox makes it really easy for you to test your handlers. So no more excuses, get in to the best practice of unit testing and now unit test your application code. Enjoy.


Copyright 2006 ColdBox Framework by Luis Majano