Sugar logic hooks are the bread and butter for Sugar developers. There are great resources from Sugar on how to create logic hooks but not so much on how to test them.
A logic hook is a pretty simple pattern where the code is a function call with three arguments: a Sugar bean, an event, and optional parameters. A good unit test wants to test a small piece of code in isolation but this seems impossible with the logic hook pattern. You are given three arguments and it appears that you have no control over what they contain.
In this post, you will learn a way to wrest control from Sugar and test your logic hook in isolation. While this example works through a real life case of a Sugar logic hook, you will also learn ideas that can be applied to legacy code that is currently impossible to test.
Original Logic Hook Implementation
The code is simple and should work OK, so why test it? And how can you unit test it?
In reality, the code is a bit messy, has side effects, and is tightly coupled to a customized SugarCRM environment.
Alas, this code is not unit testable in its current form, mainly because you can’t isolate it.
Hmmm…maybe we actually can isolate it with some changes. Let’s give it a go!
Step 1: Create Instance Variables for Function Arguments
First, we have to deal with function arguments. This step is very easy. It doesn’t change behavior at all, but it does start us down the path of isolation.
Note this step is not necessary if you plan on keeping all of your logic in one method. I will show a simplification at the end of this tutorial that skips this step. But for the time being, let’s follow the more general case of refactoring:
That didn’t hurt at all!
Next we need to get rid of those pesky
new instances and use of globals. Note I’m not going to bother with the
new DateTime since we’re not testing
DateTime and have already taken steps to isolate its argument
$this->bean->expected_close_c. The lesson here is about isolation more than new objects.
Step 2: Create Instance Variables for New Objects
Another easy step with no behavior change! Note the code is still not testable. We are just taking baby steps to minimize introducing bugs.
Now on to
globals. I see one! Let’s fix it.
Step 3: Create Instance Variables for Globals
Onward to isolation. Let’s separate the initialization of the instance variables we just created from the body of code.
Step 4: Create Method to Initialize Instance Variable Attributes
There is still no behavior change even after refactoring. Note the method name
init is not special. I think it’s a reasonable convention but feel free to use another name, especially if you already have an
init method. Note that the
init method looks vaguely familiar. That’s intentional. It stands in for a constructor and will be a key to making the code testable.
One final step remains. Isolate the code you wish to test from everything else. This is quite simple with the previous prep work out of the way.
Step 5: Isolate the Unit of Code for Testing
Congratulations! You now have testable code!
Notice that we have not really changed the original code in any substantial way. This is good in that our changes so far are likely to cause no damage. The refactored code behaves the same as the original. No functionality has been added other than allowing the code to be unit tested.
In Part 2, we will develop a test suite for our logic hook using both assertion and behavioral testing strategies. Both strategies are useful and needed to thoroughly test our logic hook.
Ready to get your testing on? Click here to read Part 2 of this series.