Reputation: 3442
Some example code first...
The enum:
public enum TestEnum {
YES,
NO
}
Some code:
public static boolean WorkTheEnum(TestEnum theEnum) {
switch (theEnum) {
case YES:
return true;
case NO:
return false;
default:
// throws an exception here
}
}
Problem:
The TestEnum is something I import from a different code of a different developer. So it actually could change. For this case I want to have a unit test that actually checks for that non existing value. But I simply don't know how to do it with Mockito and JUnit.
This part is of course not working:
@Test(expected=Exception.class)
public void DoesNotExist_throwsException() throws Exception {
when(TestEnum.MAYBE).thenReturn(TestEnum.MAYBE);
WorkTheEnum(TestEnum.MAYBE);
}
I found one example that usees PowerMock, but I couldn't get it to work with Mockito.
Any ideas?
Upvotes: 19
Views: 25760
Reputation: 14829
Building on the answer from @assylias, I think this is the best you can do:
List<String> unknown = new ArrayList<>();
for (TestEnum e : TestEnum.values())
unknown.add(e.name());
unknown.removeAll(Arrays.asList("YES", "NO"));
if (unknown.isEmpty()) {
// Not possible to reach default case, do whatever you need to do
} else {
TestEnum notIncluded = TestEnum.valueOf(unknown.get(0));
workTheEnum(notIncluded);
}
It isn't possible (AFAIK) to fake a non-existent enum
value in a switch
statement, due to the way that enum
switch statements are compiled. Even if you resort to fiddling with the internal ordinal
field in the enum
instance via reflection, the switch
statement will give an ArrayIndexOutOfBoundsException
rather than falling through to the default
case.
Here is some code that looks like it might work, but doesn't, due to the ArrayIndexOutOfBoundsException
mentioned above:
TestEnum abused = TestEnum.YES;
try {
Class<?> c = abused.getClass().getSuperclass();
Field[] declaredFields = c.getDeclaredFields();
Field ordinalField = null;
for (Field e : declaredFields) {
if (e.getName().equals("ordinal")) {
ordinalField = e;
}
}
ordinalField.setAccessible(true);
ordinalField.setInt(abused, TestEnum.values().length);
workTheEnum(abused);
} catch (Exception e) {
e.printStackTrace(System.err);
}
OK, here is something that might work for you. It's pretty hacky, so to me it's probably worse than not having 100% code coverage, YMMV. It works by replacing the enum ordinal lookup arrays with arrays containing all zeros, which falls through to the default case.
// Setup values - needs to be called so that
// $SWITCH_TABLE$FooClass$BarEnum is initialised.
workTheEnum(TestEnum.YES);
workTheEnum(TestEnum.NO);
// This is the class with the switch statement in it.
Class<?> c = ClassWithSwitchStatement.class;
// Find and change fields.
Map<Field, int[]> changedFields = new HashMap<>();
Field[] declaredFields = c.getDeclaredFields();
try {
for (Field f : declaredFields) {
if (f.getName().startsWith("$SWITCH_TABLE$")) {
f.setAccessible(true);
int[] table = (int[])f.get(null);
f.set(null, new int[table.length]);
changedFields.put(f, table);
}
}
workTheEnum(TestEnum.YES);
} finally {
for (Map.Entry<Field, int[]> entry : changedFields.entrySet()) {
try {
entry.getKey().set(null, entry.getValue());
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
}
Upvotes: 6
Reputation: 550
You can mock, hack or trying to make it work but there is quite simple way how to do this. I assume that you are working with maven or gradle so you have main
and test
profiles.
Then in main profile you have code as above:
package my.cool.package;
public enum TestEnum {
YES,
NO
}
but then in test profile you can have another one:
// EXACTLY SAME as above
package my.cool.package;
public enum TestEnum {
YES,
NO,
INVALID_FOR_TEST_ONLY
}
and now you can use new value INVALID_FOR_TEST_ONLY
in test and it won't be available in prod profile.
There are two disadvantages:
Upvotes: 2
Reputation: 1134
With the help of Powermock we can achieve this as Powermock supports mocking of final classes
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(Trail.class)
public class TrailTest {
@Mock Trail mockTrail;
@Before
public void setUp() {
PowerMockito.mockStatic(Trail.class);
BDDMockito.given(Trail.values()).willReturn(new Trail[]{Trail.YES, Trail.NO, mockTrail});
BDDMockito.given(Trail.valueOf("YES")).willReturn(mockTrail.YES);
BDDMockito.given(Trail.valueOf("NO")).willReturn(mockTrail.NO);
}
@Test
public void test() {
assertTrue(BasicTrails.find(mockTrail.valueOf("YES")));
assertFalse(BasicTrails.find(mockTrail.valueOf("NO")));
try{
Trail aDefault = mockTrail.valueOf("default value");
}catch (Exception e) {
System.out.println(e);
}
}
}
Upvotes: 1
Reputation: 6855
Mockito
doesn't support mocking of enum values but powermock
does.
Try this.
I have created my own classes to simulate them. Please map to your own classes.
@RunWith(PowerMockRunner.class)
@PrepareForTest(Trail.class)
public class TrailTest {
@Before
public void setUp() {
Trail mockTrail = PowerMock.createMock(Trail.class);
Whitebox.setInternalState(mockTrail, "name", "Default");
Whitebox.setInternalState(mockTrail, "ordinal", 2);
PowerMock.mockStatic(Trail.class);
expect(Trail.values()).andReturn(new Trail[]{Trail.YES, Trail.NO, mockTrail});
expect(Trail.valueOf("default value")).andReturn(mockTrail);
PowerMock.replay(Trail.class);
}
@Test(expected = RuntimeException.class)
public void test() {
Trail aDefault = Trail.valueOf("default value");
BasicTrails.find(aDefault);
}
}
This is the method :
public class BasicTrails {
public static boolean find(Trail trail) {
switch (trail) {
case YES:
return true;
case NO:
return false;
default:
throw new RuntimeException("Invalid");
}
}
This is the enum
public enum Trail {
YES, NO;
}
Upvotes: 4
Reputation: 328598
How about a simple:
Set<String> expected = new HashSet<> (Arrays.asList("YES", "NO"));
Set<String> actual = new HashSet<>();
for (TestEnum e : TestEnum.values()) actual.add(e.name());
assertEquals(expected, actual);
(using HashSet rather than ArrayList because order does not matter)
Upvotes: 8