A while back, I (Marc) wrote about using coldfusion's "mix-in" functionality to achieve simple mocking. The usefulness here is where you have a function that "does something", but you want to change the thing that it does for the purpose of a test. This is particularly handy when you're testing functionA(), and functionA() calls functionB() and functionC(). Maybe functionA() depends on the results of functionB() to do certain work, and then it calls functionC() to do other work. In code, it might look like:
And here might be some tests for functionA:
Now, let's say functionB() queries the database or whatever, based on the passed-in someArg argument. The problem is obvious: your database is in an unknown state, because data change all day long. And you want to do a number of tests: you want to test the condition where functionB() returns a single list element, and also when it returns more than 1 list element. Which means you need at least two known inputs for someArg: one that will ensure functionB() returns a single element, and one that ensures it'll return more than one. What a pain! Wouldn't it be great if you could say "for the purposes of this test, I want functionB() to return a single list element". and then in another test, say "And for this test, I want it to return 2 list elements"? Or, to put it another way, wouldn't it be nice to override functionB for this test, but without a lot of work?
This is why injectMethod() was born. To make it a little easier to override functions for the purpose of testing. Now, you're not overriding the function under test! You're overriding functions that the function under test calls, in order to make it easier to test the function under test.
Here's the method signature for injectMethod():
- "Receiver" is your component under test
- "Giver" is your test itself since that's where the overriding function will be declared, so you'll use "this"
- "functionName" will the name of the overriding function you've defined in your test that will be used to overwrite the function in the component under test
- "functionNameInReceiver" is the function you're overriding
Let's have a look at our new set of tests:
As this illustrates, we've now created a very easy way to test functionA with the 3 cases we need to happen with functionB: a single list, multiple list, and no-element returns. Now, to take this one step further, you could override functionC – which, if you remember, updates the database – with a simple function that simply returns "true". Remember, we're not testing functionC so ideally we wouldn't touch the database at all in this case
There you go: you can pass in functions to achieve exactly the conditions you want to achieve in order to fully test your logic. And you pass in functions that "spoof" the DB-updating function that would slow down your test and potentially corrupt your data.
I can't stress enough that this solves a different problem than mock objects solve. Mocks solve the problem of replacing full-featured collaborator objects with controlled-behavior stand-ins at time of test. But in this case, we're not spoofing functions in a dependent component. We're spoofing functions in the same component we're trying to test.
|We strongly encourage you to write tests that use freshly-created instances in your setUp() method.|
If you have tests that reuse the same component instance, injectMethod() can wreak havoc. Usually you wind up in this situation if you're using coldspring-managed objects inside your TestCase. To undo a method overwrite resulting from injectMethod(), you can use restoreMethod( object, "functionName" ). This will restore the original function back into the component.
MXUnit will NOT automatically restore methods for you. You must do so deliberately. I advise doing it in tearDown, like so: