Photo from Chile

Faking ColdFusion Component Types (and a Quick Way to Confuse ColdFusion)

MightyMock (MM) is a mocking framework for ColdFusion that is bundled with MXUnit. It allows you to create mocks, which are fake instances of ColdFusion components, for use in unit tests. More information on what MightyMock is and how you can use it to create awesome unit tests can be found on the MXUnit Wiki.

One thing that MM allows you to do is to create typesafe mocks, which are mock objects that ColdFusion will recognize as being of a specific type. Why might you need to do that? Let's say you have a setter defined in a component into which you want to pass your mock object, and that setter expects an argument of a certain type. For example, consider this setter in a Customer.cfc component:

view plain print about
1<cffunction name="setValidator" access="public" returntype="void">
2    <cfargument name="validator" type="model.util.validator" />
3    <cfset variables.validator = arguments.validator />
4</cffunction>

If we want to create a mock object and pass it into that method, it needs to have a type of model.util.validator or ColdFusion will throw an error as soon as we call setValidator(). MM allows us to create such a mock using the optional second parameter of the mock() method that is available in MXUnit. The code to create a test for our Customer component using a typesafe Validator mock would look something like this:

view plain print about
1<cffunction name="someTestThatNeedsTheValidatorInCustomerToReturnTrue" access="public" returntype="void">
2    <cfset mockValidator = mock("model.util.validator","typesafe") />
3    <cfset mockValidator.validate("{*}").returns(true) />
4    <cfset customer = new model.customer() />
5    <cfset customer.setValidator(mockValidator) />
6    <!--- call a method on the customer object that
7        expects the composed validator object to return true --->

8    <!--- assert something here --->
9</cffunction>

For the purposes of this post I'm not going to get into how and why we're using the mock. That may or may not be evident to you from the code. I simply wanted to point out that MM can create a mock of a particular type using the typesafe argument. There's just one issue with the current implementation of typesafe in MM: the component model.util.validator must exist. The way MM works is that it will create an instance of that component and then empty it out, so if model.util.validator doesn't exist you'll get an error when you try to create the mock.

I wanted to find a way around this limitation, as often one is using a mock precisely because the component being mocked has not been written yet. Sure, you can just create an empty component and you're good to go, but I thought that I could find a way around that requirement. I actually got this idea from a presentation that I saw at CFUnited by Elliott Sprehn, and I wanted to try it out. It turned out to be incredibly simple, but also had a side effect that could be very dangerous.

It turns out that you can change the metadata of a component simply by setting it. What do I mean by that? Here's an example, using the same unit test as above, creating a typesafe mock manually:

view plain print about
1<cffunction name="someTestThatNeedsTheValidatorInCustomerToReturnTrue" access="public" returntype="void">
2    <cfset mockValidator = mock() />
3    <cfset getMetadata(mockValidator).name = "model.util.validator" />
4    <cfset mockValidator.validate("{*}").returns(true) />
5    <cfset customer = new model.customer() />
6    <cfset customer.setValidator(mockValidator) />
7    etc...
8</cffunction>

Notice what I did there? Rather than using the typesafe argument to the mock() method, I just created a simple typeless mock. Then, by setting getMetadata(mockValidator).name to model.util.validator, I changed the actual metadata of the component. Now, even though my component is actually an instance of mxunit.framework.mightymock.MightyMock, ColdFusion thinks it's an instance of model.util.validator, and I can pass it into my setter. Pretty cool, eh? So what about the side effect?

It turns out that ColdFusion caches this metadata, so after making that initial change to that one instance of mockValidator, any mock that I create will have a type of model.util.validator. Of course I can set it back, which is what I may end up doing if this technique finds its way into MightyMock, but until I do that the changes to the metadata will persist. Any intance of mxunit.framework.mightymock.MightyMock will be seen by ColdFusion as an instance of model.util.validator. So where does the Quick Way to Confuse ColdFusion part come in?

When I was experimenting with this technique at one point I tried overwriting the extends metadata of the mock. A mock, which, as I mentioned already, is an instance of mxunit.framework.mightymock.MightyMock, does not extend anything. This means that it in fact extends the base ColdFusion component, which is WEB-INF.cftags.component. The test in which I tried overwriting the extends metadata looked like this:

view plain print about
1<cffunction name="someTestThatNeedsTheValidatorInCustomerToReturnTrue" access="public" returntype="void">
2    <cfset mockValidator = mock() />
3    <cfset getMetadata(mockValidator).extends.name = "model.util.validator" />
4    etc...
5</cffunction>

What do you reckon that code did? That's right, it overwrote the metadata for the base ColdFusion component. Now every single component that does not extend anything looks like it extends model.util.validator. In addition to that, even components that do extend something still end up looking like they extend model.util.validator at the top of their inheritance hierarchy. This broke MXUnit immediately as it expects arguments of type WEB-INF.cftags.component. What else this might break would depend on the code that you're writing, but considering that it does change the inheritance hierarchy of all ColdFusion components, it seems like a pretty dangerous thing to do. Sure it's cool, but it's not something I'd recommend doing.

TweetBacks
Comments
Hi Bob,

Funny, after reading the first paragraph or two, I was immediately thinking about Elliott Sprehn's presentation and the cached component metadata ;-)

That said, it seems way too risky for MM. Unless I'm missing something, the only way to safely use that trick would be to lock the lifetime of each instance, right? In fact, you wouldn't even be able to use two mocks leveraging that technique in the same test, right? Because you'd be mocking two different types, but the metadata will only show one type.

I guess MM would have to fall back to writing CFCs to disk. That said, this trick can still be useful. Rather than worrying about paths, mappings, etc. when generating a mock CFC to disk, you could write an empty CFC to disk with a unique name. Something like this:


mockType = 'model.util.validator';
mockName = replace( createUUID(), '-', '', 'all' );
mockPath = "#expandPath( '/mxunit/mightymock/temp' )#/#mockName#.cfc";
mock = createObject( 'component', 'mxunit.mightymock.temp.#mockName#' );
getMetadata( mock ).name = mockType;
// go nuts!

Just a thought!

Cheers,
Jamie
# Posted By Jamie Krug | 11/10/10 9:43 AM
You're right Jamie. I thought of that shortly after writing this post. Using this technique wouldn't work for MM at all for the reasons you've stated.

We could look at writing an empty cfc to disk, as you describe, but that seems like a lot of work just to prevent folks from having to create an empty cfc for their mocks, so I think perhaps leaving it as is makes the most sense at this point.
# Posted By Bob Silverberg | 11/10/10 9:50 AM
Yeah, I mentioned that the metadata is shared in the presentation too. ;) It's useful for code generation, but modifying instances of framework objects doesn't work well because the metadata is shared across all instances.

That's why the example I usually use is proxies in Transfer ORM. There Mark was already doing code generation and people were using type safe decorators. When he introduced proxies he was stuck (since the TransferProxy was the wrong type) and people had to remove all the typing from their code to enable proxies. An alternative would have been to write out a component that extends TransferProxy for each proxy that faked its type. With the amount of code generation he was doing, this would have been easy.

If you're on CF9 it's really quite trivial to write out a component to the ram filesystem and use that. If you're on CF8 it's possible too, but it requires some compiler hackery with the TemplateProxyFactory. OpenBD and Railo have supported ram fs for years IIRC.

Anyway, I'd argue that there is value in not making users create those empty components. In a large system you might have lots of CFCs so the number of mocks can grow quite large too. You also end up needing to refactor the empty mocks if you move components around. With this approach neither is an issue.
# Posted By Elliott Sprehn | 1/23/11 6:42 AM