For the longest time, I never used mocks in unit tests. I guess I never truly understood them. Today, I finally "got" it. Or, at least, I now get one slice of the pie.
Here's a function I want to test. It's named rollBackFastPlanFromProduction:

This is pretty simple code: basically, see if there are any outstanding orders. If Not, run the updatePlanStatus function; otherwise, just set a message and return the result struct. (NOTE: this method of returning structs is not best practice but it follows a model we have at work and thus I'm choosing consistency in this case. )
Ok, so, I want to test this code. All I want to know is:

When there is a single outstanding order, does the update function NOT run, and does the struct return a false "success" key

When there are multiple outstanding orders, does that same behavior apply

When there are no outstanding orders, does the update function run and return a true "success" key

That's pretty much it. I'm not interested in the particulars of the message. And I surely don't care about the particulars of the results of UpdatePlanStatus... that code may or may not be tested elsewhere but it's not this test function's job to test that.
Right away, here are the problems:

how to create the conditions for a single outstanding order

how to create the conditions for multiple outstanding orders

how to get the UpdatePlanStatus function to NOT do anything (it updates the DB, and I don't want to touch the DB in this test)

how to get all of this to work without needing real data

This, in fact, is one of the core problems you seem to always run into in unit testing: how to set up your conditions without jumping through a million hoops.
Back in the day, I'd have done this:

Go into the database and find an ID in the appropriate table that would give me a single outstanding order

Do the same for multiple outstanding orders

Find an ID for no outstanding orders

Don't worry about the details: the point is that I'd have found data that matched the criteria I was trying to achieve and then, in the unit test, I would have literally used those IDs as arguments to my functions.
This would NOT have solved my problem of not wanting to actually run any DB updates. It'd have just gotten the logic to work out.
Well... it'd have gotten it to work out until the data in the DB changed. Then, all I'd have is a brittle test. The thing is that you want your tests to work all the time, even a month after you wrote them. Creating tests that depend on certain conditions in the DB itself are a sure way to brittle tests. I know because I've written dozens upon dozens of these kinds of tests.
Now then, onto Mocks.
For this post, I'm not going to use a mock framework. Just good old fashioned ColdFusion.
Here are my goals:

replace the UpdatePlanStatus function inside my object under test with a new version of the function that simply returns the string "updated".

replace the selOutstandingOrdersList function with 3 different functions, depending on what i'm trying to test. In one test, it'll return an empty string. In another test, it'll return a single number. And finally, it will return a comma-delimited list of numbers.

There, that's better. I created a new, private function inside my test named "mixin". So, up in setUp, I'm attaching my new function to the pdd object.
What mixin does is a tiny bit of CF magic. Mad props to Nathan Strutz, because without this post I'd still be banging my head against the wall. More on that later. To see what it does, let's look at the next chunk of code. A new, mock function:

It's private so that MXUnit won't run it. All it does is return the string "updated". This means nothing until you look way back up top and see that the function under test makes a call to updatePlanStatus. And that's one of the functions I want to overwrite. So... how to get my new function into my pdd object? Mixin!

See the call to pdd.mixin("updatePlanStatus",updatePlanStatus)? That's the magic. I'm overwriting the original function with my new braindead mock. This way, I don't touch the DB and I know if that function was run because if it is, it'll return the string "updated". But let's not worry about that right now, because it's honestly not important. The important part is that I've now got a really simple way to do mocking in CF when I need it.
Note the next line in the function:

So, as you see, it just returns a number, 1111 in this case. Thus, in the test above, what I want to ensure is that when the rollbackFastPlanFromProduction function is called, and it calls selOutstandingOrdersList, that that function returns a single-element list.
Guess what I do to test multiple orders? If you're thinking "Create a new function that returns a multi-element list, and mixin that function instead... you're catching on nicely.

Now, I said I'd get back to why I'm thanking Nathan Strutz. I started this test with really simple code I thought would work:
set pdd.updatePlanStatus = updatePlanStatus
i.e. I had my new, private updatePlanStatus function and I wanted to overwrite the existing version in the object. Pretty simple, right? and when I called pdd.updatePlanStatus() directly, it did indeed return the string "updated". However, when the rollbackFastPlanFromProduction() function called updatePlanStatus, it ran the original code.
Normally, I'm pretty good at figgerin' out this stuff. But after a half hour or so, I went all last-resort: I googled it. I don't know how, but I ran into Nathan's post, and he discusses what I honestly didn't know: that when functions call each other internally, they're calling the version of the function that lives in the variables scope. And when you, dear programmer, call a function on an object directly, you're calling it in this scope. So what I did not understand was that functions had two lives, so to speak: a life in variables scope, and a life in this scope. When I was overwriting the function using the direct cfset, I was overwriting it in the external -- this -- scope. But not the internal one.
This brings me to the mixin function: the key line is this one:

<cfset variables[method] = value>

This little beaut right here overwrites the internal life of the function, and that's how I can create the mocks.
So, thanks Nathan!
Now, I leave you with some thoughts:

this mixin business seems pretty handy, and I'd probably want to do it in all kinds of objects. Am I going to have to copy that function into every testcase?

And wouldn't it be nice to NOT have to create those simple little private functions?

And wouldn't it be nice to say "Hey, object, in this case, when you had a single outstanding order, did you NOT run that updatePlanStatus function? And in that other case, where you had no outstanding orders, did you run it?

This is where a framework for creating mock objects becomes valuable. And that's where I'll pick up next.
Until next time... happy testing.

5 comments:

dude, cohiba++. anxious to see what you come up with cfeasymock and coldmock. very interested, too, to see how and if you find benefit from behavioral verification (vs. state-based verification), and how to skin that cat ... not sure where _that_ phrase came from, but, the sound seems very appropriate when trying to refactor code to make it more testable.

cool, glad it helped, Bob! Bill's been doing a lot with cfeasymock and i hope he blogs more about it soon. Also, the whole "hey, this mixin thing might be handy" thing in this blog post made its way into a tiny bit of new functionality in the 1.0 release of mxunit... we've not got a simple "injectMethod" function that all test cases have which lets you easily do this quick and dirty mixin mock stuff... for those who aren't ready for or don't yet need a mock framework.