The untestable enum switch in Java
If you are a Java developer you have at some point written a switch statement based on Enums. This is perfectly normal and in some cases the “only” way how to solve your problem. I wouldn’t have looked for alternatives maybe if there was a nice clean way to Unit test this without using additional test-suite libraries (like PowerMock).
Let’s assume we have the following code:
public enum Action {
EAT,
SLEEP,
DRINK,
CODE
}
And in some other class we have a switch statement like the following:
public class ActionDelegator { public void delegate(Action a, Data d) {
switch(a) {
case EAT:
eat(d);
break;
case SLEEP:
sleep(d);
break;
case DRINK:
drink(d);
break;
case CODE:
code(d);
break;
default:
throw new RuntimeException("Action unknown");
}
}
}
In order to keep this post short we assume that these methods are implemented according to our needs and the Data object can be anything on which we want to perform the actions.
When we write such code and we want to unit test it we can do it without providing ‘default’ case, but the compiler and some code styling tools will complain about this and in general the more correct way is to introduce a default block, to be safe from all aspects. Doing so I came across the issue that I cannot test the default case with normal JUnit testing suites.
What I would like to show in this article is an alternative that not only avoids using a switch, but it can also be Unit Tested.
The alternative that I will present is taking advantage of Java data structures and functional interfaces. The way the switch statement was rewritten is the following:
public class ActionDelegator{ private final Map<Action, Consumer<Data>> delegators = Map.of(
EAT, this::eat,
SLEEP, this::sleep,
DRINK, this::drink,
CODE, this::code
); public void delegate(Action a, Data d) {
Optional.ofNullable(delegators.get(a))
.ifPresent(consumer -> consumer.accept(d));
}
}
This way we explicitly limit which choices do we cover, we protect ourselves from null inputs and we have testable code.
This can of course be extended and improved if we want to explicitly handle unknown cases, but in this scenario this was not required.
I hope you find this useful and until next time.