I recently ran into a situation where I needed to test some code that used an enum to select between various strategies. As a toy example of this, we’ll use Accounts which have a list of investments and a Strategy which defines what investments are acceptable.
Strategy is a Java 5 enum that provides a method to define what’s acceptable.
This gives nice simple code. Strategy is also easy to test — we can test each of the enum values separately as appropriate to those strategies. Here’s one of the tests.
Testing the account is a little bit harder. We don’t want to implicitly test Strategy while testing the account. What we want to test is: assuming Strategy works correctly, what’s the correct behavior of account. And I’m sure by now you’re already typing the double braces of a jMock Expectations block, which is exactly what we need. The Strategy takes some input, and gives back a result. We want to know that:
- Account is passing the parameters to isRiskAcceptable correctly, and,
- Account is behaving correctly based on what isRiskAcceptable returns.
For that, we need a mock instance of Strategy. Mockery defaults to only being able to mock interfaces, so I need to install the ClassImposteriser. With this, jMock uses Objenesis to instantiate the objects rather than using CGLib to create a class implementing the mocked interface. The fact that the enum class extends Enum doesn’t matter to jMock.
As you can see in AccountTest, this lets me test Account’s functionality in isolation. Because I’m supplying a mock Strategy, this test won’t break when I change the rules in Strategy. In fact, I didn’t make any attempt to match the parameters to the results; I just supply different values to make sure I didn’t mix up the parameters somewhere along the way and to make it easier for the next person coming along to read the intent of the test.
While on the subject of jMock style, purists might argue that since isRiskAcceptable is just providing a value rather than causing any side effect, I could use allowing instead of oneOf and leave off the assertIsSatisfied. In this case, you would refer to the dynamically created instance as a stub rather than a mock.
A note on the “right” solution to this.
You can also get the same behavior by mapping your strategies using Hibernate with single table inheritance. Unfortunately, you’d still need to use the class imposteriser or open yourself up to the possibility of runtime errors when someone tries to set an unmapped instance of the interface as the strategy on your object. It avoids the issue of switch because you no longer have switch available &emdash; you need to either put the logic in the class and use method dispatch or use a visitor.
This isn’t a bad solution, but it’s a heavier solution more appropriate where the strategy really captures a big chunk of functionality. Of course, there’s nothing saying that you can’t make your core object polymorphic, but this is rarely a good solution if the sub type of the objects can change.
So there we are. If you clone Wealthfront’s github repository and run AccountTest, you’ll see it passes, and if you’re skeptical, you can try changing something and watch the tests fail. We’re all done, right?
Well, maybe. Unfortunately, these surreptiously created instances of the enum may stand in for some uses, but enums are still special, and you can run into problems elsewhere.
One other common (well, as common as any of this stuff is) reason to want to mock an enum is to allow testing default cases in switch statements that already cover all values. For example, if I wanted to turn the above Strategy enum into a human readable description, I might do something like this:
This is not so easy to test. While calling methods on enums behaves just like calling methods on any other object, but switch actually uses the ordinal value of the enum for efficiency. The mockery solution I suggest above will set an ordinal value of 0, and so follow the path for the first enum value. Using reflection to set the ordinal value (e.g. to Strategy.values().length) just makes it fail differently. In other words, we can easily create an instance of the enum, but we can’t make the static fields in the associated class aware of this instance. Solutions to this require more abuse of the JVM than I think is worth it, but those interested can see Java Specialists’ post on Hacking Enums.
The reason I’m not too worried about this limitation is because we tend not to switch on enums, but instead favor using the Visitor pattern which gives the effect of type-safe Scala pattern matching on sealed case classes in Java. I’ll go into the details in a future post.
The full example as a maven project is available in Wealtfront’s github.