Using Mocks during record state

As we've started using easymock here in our CF development I've logged a few issues that have occurred when developers who aren't familiar with mocks begin using them in their unit tests.

Take the example below:

<cfcomponent name="ExampleTest">
<cffunction name="setup" returntype="void">
<cfset mf = createObject( "component", "easymock.MockFactory" ) />

<cfset mock = mf.createMock( "component.to.Mock" ) />

<cfset test = createObject( "component", "Example" ).init( mock ) />
</cffunction>

<cffunction name="testFunction" returntype="void">
<cfset mf.expect( mock.functionCall() ).andReturn( true ) />

<cfset mf.replay( mock ) />

<cfset assertTrue( test.function(), "function() failed" ) />

<cfset mf.verify( mock ) />
</cffunction>
</cfcomponent>

At first everything looks fine. The mock is created and passed to the component under test in the setup. The expected functions are called on the mock and it is set to replay. The test function runs and then the mock behavior is verified.

But what you don't see here is what happens to the mock when it is passed to the Example component in init().

<cfcomponent name="Example">
<cffunction name="init">
<cfargument name="obj" />

<cfset foo = arguments.obj.otherFunction() />

<cfreturn this />
</cffunction>
</cfcomponent>

What's this? A function call is made on the component we pass into Example.init(), in this case our mock. Since the mock has not been set to replay, the otherFunction() call is recorded. Which will cause the test to throw an error on verify.

To remedy this, the initialization of the Example component simply needs to happen in the test function, after replay.

Make sure your mocks are in replay mode before using them.

Named vs Unnamed Arguments in CF functions

ColdFusion gives developers a great deal of flexibility when creating functions. Not all arguments have to be known at runtime by declaring them with a tag. Any additional arguments passed get a number key relative to their position (if they aren't passed in by name or via an argumentcollection).

So for function foo...

<cffunction name="foo">
<cfargument name="bar" /.
...
</cffunction>

called by

<cfset foo( 10, 20, 30, 40 ) />

Would yield an argument struct of: bar = 10, 2 = 20, 3 = 30, 4 = 50

If you call foo like the following:

<cfset foo( bar=10, var1=20, var2=30, bar2=40 ) />

you would get an argument struct of: bar = 10, var1 = 20, var2 = 30, bar2 = 40

At first I thought this was a great concept. Less typing, not constrained to a particular implementation, etc. Then I got to thinking. This could really turn into a slippery slope.

Functions that don't have a clear set of arguments can easily end up not having a clear definition of purpose. (Not to mention difficult to debug!) They could start to take on more and more behavior and then lose any resemblance of reusability.

Functions that perform one specific behavior will have a known set of inputs. Those input values may vary, but the inputs themselve are always known.

There are some instances where unnamed arguments does make things a lot easier. There's even an example of that within CFEasyMock itself, in the MockFactory.replay() method (and reset() and verify()). It is simply easier to pass in a series of components rather than make an array out of them first. (Which would be the most prudent way to do it.) However, there is still the chance that someone passes in the wrong object, but that wouldn't be caught until runtime anyway.

It's a double edged sword. There are definite benefits, but it should be used sparingly with good reason, as opposed to a general coding practice.

BlogCFC was created by Raymond Camden. This blog is running version 5.5.006.