I have util method to process some text. The way of processing is determined by enum Mode
. While writing unit test to cover this method, I had to add dummy enum
element in order to include default
part of switch
statement into testing. Default part is my check for the case if enum
is extended by new element and this new element is not added to switch
statement meaning processing would be impossible for that new element. If I remove dummy element and try with null
, then I get NPE and Java don't even enter switch
statement - which is normal behavior when you use null
for switch
statement.
The question: Is there a way to avoid adding dummy enum
element but still cover default
part of switch
statement in unit test?
Addition for clarity: Java 17, JUnit Jupiter 5, Eclipse Code Coverage Tool
Code
abstract class LineBreaker {
public enum Mode {
LAST_SPACES,
LAST_CHARS,
DUMMY
}
public static String process(
final String text,
final LineBreaker.Mode mode,
final int amount
) {
switch (mode) {
case LAST_SPACES : {
// blah blah create `processingResult`
return processingResult;
}
case LAST_CHARS : {
// blah blah create `processingResult`
return processingResult;
}
default :
throw new IllegalStateException(
"Processor for " +
LineBreaker.class.getSimpleName() + "." +
LineBreaker.Mode.class.getSimpleName() + "." +
mode.name() +
" not implemented"
);
}
}
}
Unit test
@Test
void testLineBreakerException() {
assertThrows(
IllegalStateException.class,
() -> LineBreaker.process( "Dog", LineBreaker.Mode.DUMMY, 10 ),
"Expected " + IllegalStateException.class.getSimpleName()
);
}
Unit test without additional dummy enum element
Code coverage without additional dummy enum element
Unit test with additional dummy enum element
Code coverage without additional dummy enum element
You don't need to create or extend the existing enum Mode
. You just need to mock this enum with Mockito.
For this, you can use Mockito version 5 or later, which support mockito-inline as the default mockmaker. This will support mocking static method easily.
testImplementation group: 'org.mockito', name: 'mockito-core', version: '5.11.0'
The strategy is to create a mock enum UNSUPPORTED
. Use Mockito.mockStatic
to mock the static method values()
of the enum, and return the existing values plus the mock enum UNSUPPORTED
.
Then this mock UNSUPPORTED
enum would trigger the default path of your switch-case statement, which allow you to perform the assertion easily.
Code snippet
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doReturn;
public class SOEnumQuestionTest {
public enum Mode {
LAST_SPACES,
LAST_CHARS
}
public static String process(final Mode mode) {
switch (mode) {
case LAST_SPACES : {
return "processing last spaces";
}
case LAST_CHARS : {
return "processing last chars";
}
default :
throw new IllegalStateException("Processor not implemented");
}
}
@Test
public void testProcessWithDefaultSwitchCase() {
try (MockedStatic<Mode> dummyModeMockedStatic = Mockito.mockStatic(Mode.class)) {
final Mode UNSUPPORTED = Mockito.mock(Mode.class);
// We want to give the mocked enum in the last position of the enum values list
// E.g: Enum has 2 values -> our mock UNSUPPORTED enum index is 2
doReturn(2).when(UNSUPPORTED).ordinal();
dummyModeMockedStatic.when(Mode::values)
.thenReturn(new Mode[]{
Mode.LAST_SPACES,
Mode.LAST_CHARS,
UNSUPPORTED});
Throwable expectedException = assertThrows(IllegalStateException.class, () -> SOEnumQuestionTest.process(UNSUPPORTED));
assertEquals("Processor not implemented", expectedException.getMessage());
}
}
}