Unit Testing Transfer Decorators with CFCUnit, BeanInjector, and ColdMock

Paul Marcotte asked me to post about how one might unit test a Transfer Decorator. So here you go Paul (and anyone else who might be interested) :-) .

To start with, basic testing of a Transfer Decorator is really no different than testing any other component. First, I set up a ColdSpring file to use for the test. Note that, depending on how many things you need to test and what you're testing, you can use the same ColdSpring file to test numerous things. In other words, you don't necessarily have to have a separate ColdSpring XML file for every test.

<beans>
   
   <bean id="transferFactory" class="transfer.transferFactory">
    <constructor-arg name="datasourcePath"><value>/tests/transfer/testing/datasource.xml</value></constructor-arg>
    <constructor-arg name="configPath"><value>/tests/transfer/testing/transfer.xml</value></constructor-arg>
    <constructor-arg name="definitionPath"><value>/tests/transfer/testing/definitions</value></constructor-arg>
   </bean>
   
   <bean id="datasource" factory-bean="transferFactory" factory-method="getDatasource" />

   <bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
   
</beans>

From here, the unit test might look like this (I'm using CFCUnit but the approach in CFUnit would be similar):

<cfcomponent name="TestRegisteredUser" extends="org.cfcunit.framework.TestCase">
   <cffunction name="setUp" access="public" output="false" hint="">
      <cfset var local = StructNew() />
      <cfset local.config = '/tests/transfer/testing/coldspring.xml.cfm' />
      <cfset beanFactory = CreateObject('component', 'coldspring.beans.DefaultXmlBeanFactory').init() />
      <cfset beanFactory.loadBeansFromXmlFile(beanDefinitionFile=local.config) />
      <cfset setRegisteredUser(beanFactory.getBean('transfer').get('user', 2)) />
   </cffunction>
   
   <cffunction name="test_customDecoratorMethod" returntype="void" access="public" output="false" hint="">
      <cfset var local = StructNew() />
      <cfset assertEqualsString("This is a custom method in the Decorator.", getRegisteredUser().customDecoratorMethod(), 'Custom decorator response does not match expected value.') />
   </cffunction>
   
   <cffunction name="getRegisteredUser" access="private" output="false" hint="I return the RegisteredUser.">
      <cfreturn variables.instance.RegisteredUser />
   </cffunction>
      
   <cffunction name="setRegisteredUser" access="private" output="false" hint="I set the RegisteredUser.">
      <cfargument name="RegisteredUser" required="true" hint="RegisteredUser" />
      <cfset variables.instance.RegisteredUser = arguments.RegisteredUser />
   </cffunction>
   
</cfcomponent>

As you can see, you grab the Decorator from Transfer, and then are free to test the methods in the Decorator the same way you would test any other CFC method.

To take this further, you can actually mix a number of the techniques that I've talked about to handle more complex tests. In this example, I'm using the TDOBeanInjectorObserver to wire in a ValidatorFactory to my Decorator. However, to keep the test isolated to the Decorator, I'm using ColdMock to create a mock of the ValidatorFactory and injecting that into the Decorator:

<beans>
   
   <bean id="transferFactory" class="transfer.transferFactory">
    <constructor-arg name="datasourcePath"><value>/tests/transfer/testing/datasource.xml</value></constructor-arg>
    <constructor-arg name="configPath"><value>/tests/transfer/testing/transfer.xml</value></constructor-arg>
    <constructor-arg name="definitionPath"><value>/tests/transfer/testing/definitions</value></constructor-arg>
   </bean>
   
   <bean id="datasource" factory-bean="transferFactory" factory-method="getDatasource" />

   <bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
   
   <bean id="validatorFactory" factory-bean="MockFactory" factory-method="createMock">
      <constructor-arg name="objectToMock">
         <value>tests.transfer.testing.ValidatorFactory</value>
      </constructor-arg>
   </bean>
   <bean id="MockFactory" class="coldmock.MockFactory" />
   
   <bean id="beanInjector" class="common.components.utility.BeanInjector">
      <constructor-arg name="debugMode"><value>false</value></constructor-arg>
      <constructor-arg name="suffixList"><value></value></constructor-arg>
   </bean>
   
   <bean id="TDOBeanInjectorObserver" class="common.components.transfer.TDOBeanInjectorObserver" lazy-init="false">
      <constructor-arg name="transfer">
         <ref bean="transfer" />
      </constructor-arg>
      <property name="beanInjector">
         <ref bean="beanInjector" />
      </property>
   </bean>
   
</beans>

This way, I can test any methods in the Decorator that actually rely on the ValidatorFactory by mocking the results from the ValidatorFactory method calls:

<cfcomponent name="TestRegisteredUser" extends="org.cfcunit.framework.TestCase">
   <cffunction name="setUp" access="public" output="false" hint="">
      <cfset var local = StructNew() />
      <cfset local.config = '/tests/transfer/testing/coldspring.xml.cfm' />
      <cfset beanFactory = CreateObject('component', 'coldspring.beans.DefaultXmlBeanFactory').init() />
      <cfset beanFactory.loadBeansFromXmlFile(beanDefinitionFile=local.config) />
      <cfset setRegisteredUser(beanFactory.getBean('transfer').get('user', 2)) />
   </cffunction>
   
   <cffunction name="test_customDecoratorMethodUsingValidator" returntype="void" access="public" output="false" hint="">
      <cfset var local = StructNew() />
      <cfset local.validator = beanFactory.getBean('validatorFactory') />
      <cfset local.validator.returns("This is the MOCKED ValidatorFactory method.") />
      <cfset assertEqualsString("This is the MOCKED ValidatorFactory method.", getRegisteredUser().customDecoratorMethodUsingValidator(), 'Custom decorator using validator response does not match expected value.') />
   </cffunction>
   
   <cffunction name="getRegisteredUser" access="private" output="false" hint="I return the RegisteredUser.">
      <cfreturn variables.instance.RegisteredUser />
   </cffunction>
      
   <cffunction name="setRegisteredUser" access="private" output="false" hint="I set the RegisteredUser.">
      <cfargument name="RegisteredUser" required="true" hint="RegisteredUser" />
      <cfset variables.instance.RegisteredUser = arguments.RegisteredUser />
   </cffunction>
   
</cfcomponent>

Just for reference, the Decorator I'm using in these test examples looks like this:

<cfcomponent hint="Registered User" extends="transfer.com.TransferDecorator" output="false">
   
   <cffunction name="customDecoratorMethod" access="public" returntype="string" output="false" hint="">
      <cfreturn "This is a custom method in the Decorator." />      
   </cffunction>
   
   <cffunction name="customDecoratorMethodUsingValidator" access="public" returntype="string" output="false" hint="">
      <cfreturn getValidatorFactory().validatorFactoryMethod() />      
   </cffunction>
   
   <cffunction name="getValidatorFactory" access="public" returntype="any" output="false" hint="I return the ValidatorFactory.">
      <cfreturn variables.instance.validatorFactory />
   </cffunction>
      
   <cffunction name="setValidatorFactory" access="public" returntype="void" output="false" hint="I set the ValidatorFactory.">
      <cfargument name="validatorFactory" type="any" required="true" hint="ValidatorFactory" />
      <cfset variables.instance.validatorFactory = arguments.validatorFactory />
   </cffunction>
   
</cfcomponent>

Hopefully that helps clear up how one might go about testing a Transfer Decorator. It also shows how you can use unit testing, ColdMock, and the Transfer Bean Injector together. Please let me know what you think or if you have any questions or comments about how I'm doing this.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Awesome stuff, Brian! Thank you, very much.

I'm half way there in terms of how you approach unit testing transfer decorators. The ColdMock example is exactly what I needed to get me over the hump. :)
# Posted By Paul Marcotte | 1/28/08 2:30 PM
Excellent post. Nice to see all these tools come together so well.

I will defiantly be using this technique.
# Posted By David Beale | 1/28/08 4:02 PM
First let me say I really love coldmock I've been using it for a while and it has really helped me to do more significant testing.

I'm curious about how you deal with the database in your unit testing, do you just hit a test db in a known state? Or do you do some kind of transaction rollback? Or something else?
# Posted By Jon Messer | 1/28/08 5:31 PM
I usually have a test database that I keep in a "known state" and reset (with SQL) after my tests run.
# Posted By Brian Kotek | 1/28/08 5:37 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9. Contact Blog Owner