I was at the RailsEdge conference in Chicago last week to hear some people talk about Ruby on Rails. I may post a separate entry about my thoughts on that conference and Rails, but this post goes in a different direction. One of the interesting things I saw was leveraging the dynamic nature of Ruby to create mock objects on the fly for use in your tests. It was a pretty cool idea since writing and managing "real" mock objects is kind of a pain. So I sat down and created a mock generator in ColdFusion in about an hour. I think I will call it ColdMock (not very original, I know, but I'm not a marketing guy).

If you are not familiar with Mock objects, here is the idea. When you test a CFC, you want to test ONLY that CFC. However, in a real application, CFCs often depend on other CFCs to work. It is very difficult to test these CFCs, because you're really testing the entire tree of dependent CFCs at once. To avoid this, you would substitute Mock objects for the dependent CFCs. As far as the CFC you are testing is concerned, the Mock objects act exactly the same as the real components. However, they have no actual code in them. They just return dummy data to the CFC you are testing. This way, you are only testing a single CFC, which is the entire point of a good unit test.

Up until now I have been writing (or, more often, using my CFC Stub Generator to generate) actual Mock CFC files. The problem is that you still have to go into the file and hard code return values for them. And if you need to call the method more than once during a test, you have to figure out some way to make the method return different values for the different calls. And the return values are then immutable, unless you go back in and re-edit the Mock CFC. It works, and it's definitely better than nothing, but ColdMock will make handling this much easier.

It works like this: you tell ColdMock which CFC you want to mock. It will create an instance of the original component, then purge all of its methods and dynamically attach a new set of methods that handle the mocking duties. This means that you no longer need to actually create a separate Mock CFC file. Once ColdMock gives you back your mock CFC, you declare what methods you want to mock and what values the mock method will return. Then you can test your CFC, and when it needs to call the CFCs it depends on, your Mock components will return whatever values you specified.

Since just reading the description of what it does might not make things clear, let's look at some code. I'll start with a very simple example. I want to mock out a SessionFacade component.

<!--- Create mock factory. --->
<cfset mockFactory = CreateObject('component','MockFactory').init() />
<!--- Create mock object. --->
<cfset facade = mockFactory.createMock('SessionFacade') />
<!--- Define mock return value. --->
<cfset facade.returns(32) />

<cfoutput>#facade.doSomething()#</cfoutput>
		

This creates the mock facade object, then says "when I call a method, any method, on the facade, return 32". You can also define multiple return values like this:

<cfset facade.returns(32,12,188) />
<cfoutput>#facade.doSomething()# #facade.doSomething()# #facade.doSomething()# </cfoutput>
		

Which will return a different value each time, based on the order you defined them. This works for fairly straightforward situations, but you might also need to define different values (or sets of values) for specific method calls. You can do this as well:

<cfset facade.mockMethod('getLastUserLogin').returns('1/1/2007') />
<cfset facade.mockMethod('getLoginCount').returns(42) />
<cfoutput>#facade.getLastUserLogin()# #facade.getLoginCount()#</cfoutput>
		

The return values can be anything. You can specify a string, a structure, or even a CFC instance, such as:

<cfset user = mockFactory.createMock('User') />
<cfset facade = mockFactory.createMock('SessionFacade') />
<cfset facade.returns(user) />
<cfdump var="#facade.doSomething()#" label="Dump the returned User object">
		

This should get across how the mocks work. The real benefit comes when you use them in your unit tests. You can create the mocks yourself, or if you use something like ColdSpring, you can have ColdSpring resolve the dependencies by creating mocks and injecting them into the CFC being tested.

Let's say I have a Trip CFC that I want to test. The Trip CFC depends on a SessionFacade CFC to do its work. I can create a ColdSpring XML file that my unit tests can all use which will handle the dependencies. It seems like a lot of work but it really isn't, and you probably can use one ColdSpring file to handle dependency resolution for all of your unit test files. Here is my simple ColdSpring XML:

<beans>   
   
	<bean id="trip" class="tests.coldmock.Trip">
		<property name="sessionFacade">
			<ref bean="MockSessionFacade"/>
		</property>
	</bean>
   
	<bean id="mockSessionFacade" factory-bean="MockFactory" factory-method="createMock">
		<constructor-arg name="objectToMock">
			<value>tests.coldmock.SessionFacade</value>
		</constructor-arg>
	</bean>
   
	<bean id="MockFactory" class="tests.coldmock.MockFactory" />
   
</beans>
		

Now, my CFCUnit test might look like this:

<cfcomponent name="TestTrip" extends="org.cfcunit.framework.TestCase">
   
	<cffunction name="setUp" access="public" output="false" hint="Runs before each test method.">
		<cfset var local = StructNew() />
		<cfset local.serviceDefinitionLocation = ExpandPath( '/tests/coldmock/coldspring.xml' ) />
		<cfset local.beanFactory = CreateObject('component', ' coldspring.beans.DefaultXmlBeanFactory').init() />
		<cfset local.beanFactory.loadBeansFromXmlFile(local.serviceDefinitionLocation) />
		<cfset variables.trip = local.beanFactory.getBean('trip') />
		<cfset variables.facade = local.beanFactory.getBean('mockSessionFacade') />
	</cffunction>
   
	<cffunction name="test_getDriverName" returntype="void" access="public" output="false" hint="">
		<cfset var local = StructNew() />
		<cfset variables.facade.returns('Brian') />
		<cfset assertEqualsString('Brian', variables.trip.getDriverName(), 'Driver name does not match.') />
	</cffunction>
   
</cfcomponent>
		

So now I can test my Trip CFC in total isolation. Even though it depends on the SessionFacade in my real application, I can substitute it with a mock to run my tests. I can easily define what the method calls to the mock objects will return right in my test. So I don't need to create or edit any Mock CFC files. This makes using mocks and writing good tests much easier.

Unfortunately, because it uses onMissingMethod(), it will only run on ColdFusion 8. This might not be a huge issue because most people will probably be running the developer edition of ColdFusion 8 locally. However, I'll keep trying to see if I can get it to work in a way that doesn't require onMissingMethod.

I'm still working on this but I should have something ready for release shortly. I'll be putting it up at RIAForge. In the meantime, I'm very interested to hear what people think. Any ideas, comments, or criticism of the idea?

Comments Comments (16) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 10562 Views

Related Blog Entries

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)

  • # Posted By Nate Owen | 8/28/07 3:57 PM

    Very cool! Keep up the good work. I'm new to the whole mock objects, so I really want to try this out. I'm really shooting for the ability to mock Model-Glue so I can test controller code so any ideas on that would be awesome.

    Thanks.

  • # Posted By Brian Kotek | 8/28/07 4:00 PM

    I would recommend against trying to unit test a Model-Glue controller. Use something like Selenium to run integration tests.

  • # Posted By Nate Owen | 8/28/07 4:03 PM

    I do have selenium working that way, I was just hoping, perhaps dreaming, of a way to isolate it more. You recommend that because mocking Model-Glue would be a pain?

  • # Posted By Brian Kotek | 8/28/07 4:11 PM

    Since the Controller extends the Model-Glue framework, you'd still have to instantiate the entire Model-Glue framework in order to run anything. Also, the Controller should have very little code in it anyway, just calls to the underlying model. The Selenium tests should be plenty to ensure that the Controller is working correctly (which should just be calls to the model and possibly some simple conditional logic to determine what to return or what results to apply).

  • # Posted By Jeff Chastain | 8/28/07 5:44 PM

    This is really nice. I have given up on mock objects more than once because of the time involved with manually creating and maintaining a series of "extra" objects.

    Any chance you might put together an article on Selenium and how it could be used to test a ColdFusion application? I have heard it mentioned quite a few times, but never seen any real usage of it.

    Keep up the good work!

  • # Posted By Sean Corfield | 8/28/07 9:47 PM

    Brilliant!

    onMissingMethod() is my favorite new feature in CF8 so it's great to see folks doing cool stuff with it!

  • # Posted By Brian LeGros | 8/28/07 10:02 PM

    It's great to see someone in the CF community working on a Mock object initiative. I use JMock with Java and GMock with Groovy for unit testing and I love it. With mock objects you can begin to alter your perspective on what makes up a quality unit test; assertions aren't your only tool. Most of the mock object APIs can verify dependency method calling order as well as method call integrity. Quality tests can be produced for methods which have void return types without breaking into different layers of your architecture.

    The one gotcha I've found is that mocking out your classes which interact with external resources will actually cost you more in the long run. I've found that you need to know if you can use your external resources correctly (e.g. - SQL statements, etc) with a set of sample data for regression testing to be most effective. I don't know how we'd do that in CF, but in Java/Groovy we can use things Spring's transactional AOP proxies. Maybe the ColdSpring guys could put something together to supplement unit testing in CF (or Adobe could add a meta programming layer to CF so we can alter class definitions without writing out files to be parsed).

    There is a lot of material that goes along with using mock objects as it relates to TDD/BDD as well, but that's a whole other conversation.

    Best of luck man! I could definitely use a tool like this.

  • # Posted By Gary Gilbert | 8/29/07 3:37 AM

    Brian,

    Thats a really cool usage of onMissingMethod(). Excellent.

  • # Posted By Kurt Bonnet | 8/29/07 12:53 PM

    Brian,

    VERY cool. I look forward to you posting this on RiaForge!

    Your example of setting up ColdSpring to inject your mock objects in lieu of the usual CFC reminded me of an article I read a little while ago titled "XML Merging Made Easy". Using the EL4J XML Merge library you could "patch" in changes (i.e. replace the regular bean implementation for the bean you're mocking with the mock object) to your regular ColdSpring config file which would prevent you from having to maintain 2 separate CS files (your regular CS file plus your testing CS file). I know CS supports "overriding" beans, but you have to COMPLETELY define your beans in 2 places for CS over-riding to work, where with patching, you don't.

    Anyway, I just thought this aligned well with the spirit of ColdMock and thought it was worth mentioning.

    Article "XML Merging Made Easy":
    http://www.javaworld.com/javaworld/jw-07-2007/jw-0...

    Keep up the great blog posts!

  • # Posted By John Paul Ashenfelter | 8/29/07 2:58 PM

    If you're interested in Selenium, you could take a look at the presentation and audio at http://cfunited.com/go/speakers#speaker-242 from my CFUnited talks on Selenium (and Ant) and ColdFusion.

    Or come see me in San Francisco at CFUnited Express in November. Plus I'm supposed to be doing an online talk for Charlie and company for the online users group

  • # Posted By Brian Kotek | 8/29/07 10:44 PM

    Kurt, that's interesting! But wouldn't you still have to maintain two files (one real ColdSpring file and a file with the test bean definitions that would be merged in)?

    I actually have my CFC Stub Generator generate my test ColdSpring XML file so it isn't too much of a pain since most of the work is done for me. I plan to modify that code to add an option to use ColdMock when it generates the tests and the XML.

  • # Posted By Kurt Bonnet | 8/30/07 1:23 PM

    Brian,

    Yes, you absolutely would need to have 2 files still. It's just that your second file for your "test" definitions would be MUCH smaller and you wouldn't have to maintain 2 COMPLETE coldspring files. Using this technique may or may not be helpful depending on how complex your CS beans are and to what degree you want/need to isolate something when you test it.

    The XML Merge library is pretty neat and could potentially be useful in this case so I thought I'd mention it. I wish I knew about this library a few years ago. I was actually thinking of writing my own library like this because I needed a way to customize code without touching the base/core files. This library would be a perfect way to tweak a BASE application with customizations. Basically you could have 2 projects/folders, the BASE code and your CUSTOM code, and as long as your apps use XML config for most things you could use a build process to merge the 2 code bases (cfms, htm, images, etc..) and perform xml files merges to produce a 3rd set of files (the completely customized app). Here's a concrete example. You like MachBlog but want to add a few features to it. You don't want to drop your changes directly into the MachBlog code because you want to be able to upgrade to the latest edition of MachBlog at anytime. So you have the CORE machblog folder, and then a completely separate folder that contains your NEW CFCs and CFMs, etc... as well as an XML Patch file containing the Mach-II config for your new features. Your build process could create a new folder "MachBlogBuild", and first copy all the CORE MachBlog files there. Then copy all your custom files into the "MachBlogBuild" folder - over-writing anything that had the same name (if there were any name conflicts) and then the build could perform an XML merge on the Mach-II config file using the patch in the custom code folder. Voila, a completely custom MachBlog implementation that didn't require touching a single line in the MachBlog base. If a new version of MachBlog comes out, just point your build process to the new MachBlog code base and rebuild.

    Sorry that was long and a bit off topic, but it's something I've been wanting to get out into the community for a while to get people's juices flowing around customizing applications. Someday I'll get my own blog going : )

    I wish I knew you needed some code to deal with replicating/proxying functions for ColdMock. I wrote something a while back that does similar stuff. Here's a link if you're interested.
    http://home.comcast.net/~kurt.bonnet/dynaProxyGene...

    Thanks for posting ColdMock to riaforge. I look forward to using it when I dive into unit testing in not too long!

  • # Posted By Qasim Rasheed | 9/4/07 3:24 PM

    Brian

    Excellent work!! and an interesting way to use onMissingMethod. I believe Robert has released something similar for CFUnit

    http://www.rbdev.net/devblog/index.php?entry=entry...

    Thanks

  • # Posted By Brian Kotek | 9/4/07 3:33 PM

    It is somewhat similar, but the CFUnit mock objects are actually written to disk (it actually creates CFC files). Also, the syntax he's showing seems much more verbose than what ColdMock will do. And last, from what I can see, the generated mock methods don't validate argument or return types against the original method. His implementation doesn't require CF8 though, so that is an advantage.

  • # Posted By Alan Livie | 10/26/07 3:50 PM

    I started using CFCunit and Test Driven Development 2 months ago and am now finding it a brilliant way to work.

    I used Coldmock for the first time the other day and it was really easy to stub out some composed CFC's when testing my service CFC's.

    It's another good tool in the bag. Thanks :-)

  • # Posted By Rémy Roy | 1/15/08 7:54 PM

    ColdMock is a wonderfull idea.

    I created something similar and based on your work using CF8 and the onMissingMethod function. Instead of creating a specific component for mock object, I choose to create a generic empty mock object. Since I do not restrict type and choose to use the "any" type for my methods, my generic empty mock object can be use for all kind of situation.

    I'm also planning on adding different methods to mock methods. The simple one which will always returns the same value if my first, but I was thinking about adding some new one where I could return different values based on the arguments and some conditions using the evaluate function.