Unit Testing DAOs with CFCUnit setUp() and tearDown()

Unit Testing , ColdFusion Add comments

I recently starting Unit Testing with CFCUnit. Unit testing is not a new topic in the CF community, but it is new to me. I've found that there are a lot of aspects related to unit testing (like Mock objects and stubs) that you need learn about. As I continue to read and research unit testing techniques, I've seen items that go back years. One topic that I could not find a lot of information on is how to test DAOs and the SQL code in <cfquery/> tags. After rephrasing my search terms a few times, I found a great post by Robert Blackburn that also lead me to a suggestion by Paul Kenney on the old CFCDev list to wrap the test within <cftransaction/>. I liked the idea of using <cftransaction/>, but decided to try something a little different.

One of the great features of a unit testing framework (my knowledge is limited to CFCUnit at present), is that for each test in a TestCase, the setUp() and tearDown() methods are called. The setUp() method is used to create the text fixture and tearDown() is used to cleanup the fixture. This makes it a great option to wrap each test in a transaction by adding a couple of methods to begin and rollback a transaction. Here's a snippet of code for a TestCase for a fictitious "UserDAO":

<cfcomponent name="TestUserDAO" extends="org.cfcunit.framework.TestCase">

<cffunction name="setUp" access="public" output="false" hint="">
<cfset var local = StructNew() />
<cfset local.serviceDefinitionLocation = ExpandPath( '/testsuite/coldspring.xml' ) />
<cfset local.beanFactory = CreateObject('component', 'coldspring.beans.DefaultXmlBeanFactory').init() />
<cfset local.beanFactory.loadBeansFromXmlFile(local.serviceDefinitionLocation) />
<cfset setUserDAO(local.beanFactory.getBean('UserDAO')) />
<cfset variables.transaction = 'UserTransaction'>
<cfset beginTransaction() />
</cffunction>

<cffunction name="getUserDAO" access="private" output="false" hint="I return the UserDAO.">
<cfreturn variables.instance.UserDAO/>
</cffunction>

<cffunction name="setUserDAO" access="private" output="false" hint="I set the UserDAO.">
<cfargument name="UserDAO" required="true" hint="UserDAO" />
<cfset variables.instance.UserDAO= arguments.UserDAO/>
</cffunction>

<cffunction name="tearDown" access="public" output="true" returntype="void">
<cfset endTransaction()>
</cffunction>

<cffunction name="beginTransaction" access="private" output="false" returntype="void">
<cfset var trans = variables.transaction>
<cfquery name="#trans#" datasource="#getUserDAO().getDatasource()#">
BEGIN TRANSACTION #trans#;
</cfquery>
</cffunction>

<cffunction name="endTransaction" access="private" output="false" returntype="void">
<cfset var trans = variables.transaction>
<cfquery name="#trans#" datasource="#getUserDAO().getDatasource()#">
ROLLBACK TRANSACTION #trans#;
</cfquery>
</cffunction>

</cfcomponent>

Note that the code above is for an MSSQL database. I should also mention that my DAOs also return queries so I have methods like selectAll() in addition to the standard read(), create(), update(), exists(), save() and delete() methods.

Since I'm totally fresh to the unit testing scene, I actually have no idea whether this is a terrible idea or a decent solution. Thoughts, feedback, or opinions are appreciated. I'd rather know early whether I'm on the right track...or not.


5 responses to “Unit Testing DAOs with CFCUnit setUp() and tearDown()”

  1. Brian Says:
    Paul - this is a neat idea. One of the things I've struggled with is the permanent modification of the database when testing with CFCUnit and what to do about it when a test fails and leaves the DB in the &quot;tested&quot; state. I hadn't considered a transaction but this makes rolling back changes automatic.

    I assume an example test case would run a method that changes the database, read back the record to confirm the change, then rollback the transaction?

    Now that this post is 45 days old - how is this approach working for you?
  2. Paul Marcotte Says:
    Hi Brian,

    Thanks for the feedback. You're correct regarding a test case. If I'm testing a insert() or create() method, I perform a few operations in a single test case. First selectAll() and store the number of records, perform a insert, then test that another select is greater than the last by 1.

    At the time that I needed this solution, I was building a test suite and was completely satisfied with the approach. Unfortunately, since that time, I haven't had much opportunity to work with it. :(
  3. Ilya Fedotov Says:
    Just wanted to say that I found this post useful and I am using your suggestion.
    Even though I have a dev DB, it feels much better to clean up after inserts.
    Thanks and hope to see you at CF.Objective()
  4. Paul Marcotte Says:
    Hi Ilya,

    Thanks for the comment. I use this setup with a dev database as well.

    I really looking forward to meeting people at cf.O(). My first CF conference.
  5. Christopher Bradford Says:
    One caveat to this is that if the code run during your unit test has a cftransaction tag, the rollback call in tearDown() will fail. I've solved this by following the example of the Reactor ORM framework and passing in a parameter to any functions that use cftransaction so they can run the operation without a cftransaction when necessary. This is probably good practice anyway, as the consumer of my CFCs may want to do transaction management externally.

Leave a Reply



Powered by Mango Blog. Design and Icons by N.Design Studio