Using injectMethod for simple mocking

Introduction

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:

Using injectMethod()

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():

Almost always:

  • "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.

Difference from Mocking

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.

Using restoreMethod()

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:

Labels

injectmethod injectmethod Delete
mocking mocking Delete
mock mock Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jun 22, 2010

    Anonymous says:

    Can you add the injectMethod method signature with some documentation in this pa...

    Can you add the injectMethod method signature with some documentation in this page so we don't have to look dig through the code to figure verify what each arg is? Thanks !

  2. Jun 22, 2010

    Anonymous says:

    If you use ColdSpring to resolve dependencies and set your obj in setUp(), the i...

    If you use ColdSpring to resolve dependencies and set your obj in setUp(), the injected method persists throughout the request, correct?  Is there a method to return it to it's original state?

  3. Jun 23, 2010

    Marc Esher says:

    Done!

    Done!

  4. Jun 23, 2010

    Marc Esher says:

    I strongly recommend the following: always create a new instance of your compo...

    I strongly recommend the following:

    1. always create a new instance of your component under test in the setUp function
    2. unless you want to override certain functions every single time -- which is often useful for functions that send notifications, do logging, etc -- then I suggest calling injectMethod in the test functions themselves and not in setUp.

    This ensures that the component under test, which you define in setUp, is created anew each test and is an unmodified version of the object

    In the case of ColdSpring, if you're wiring your component under test into the object once in the request, then I'd do a duplicate(coldspringInjectedObject) in setUp and use your duplicated object as the component under test

  5. Nov 19, 2010

    Marc Esher says:

    Anonymous, I've now added a restoreMethod() to undo an injectMethod(). Documenta...

    Anonymous, I've now added a restoreMethod() to undo an injectMethod(). Documentation added to this page