Enum值的unit testing不存在?

一些示例代码首先……

枚举:

public enum TestEnum { YES, NO } 

一些代码:

 public static boolean WorkTheEnum(TestEnum theEnum) { switch (theEnum) { case YES: return true; case NO: return false; default: // throws an exception here } } 

问题:
TestEnum是我从不同开发人员的不同代码导入的东西。 所以它实际上可以改变。 对于这种情况,我想要进行unit testing,实际检查该非现有值。 但我根本不知道如何用Mockito和JUnit做到这一点。

这部分当然不起作用:

 @Test(expected=Exception.class) public void DoesNotExist_throwsException() throws Exception { when(TestEnum.MAYBE).thenReturn(TestEnum.MAYBE); WorkTheEnum(TestEnum.MAYBE); } 

我发现了一个使用PowerMock的例子,但我无法与Mockito合作。

有任何想法吗?

怎么样简单:

 Set expected = new HashSet<> (Arrays.asList("YES", "NO")); Set actual = new HashSet<>(); for (TestEnum e : TestEnum.values()) actual.add(e.name()); assertEquals(expected, actual); 

(使用HashSet而不是ArrayList,因为顺序无关紧要)

基于@assylias的答案,我认为这是你能做到的最好的:

 List 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); } 

由于enum switch语句的编译方式,在switch语句中伪造一个不存在的enum值是不可能的(AFAIK)。 即使您通过reflectionenum实例中的内部ordinal段, switch语句也会给出一个ArrayIndexOutOfBoundsException而不是default情况。


以下是一些看似可能有效的代码,但由于上面提到的ArrayIndexOutOfBoundsException ,它没有:

 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); } 

好的,这可能适合你。 它非常hacky,所以对我而言,可能比没有100%代码覆盖率,YMMV更糟糕。 它的工作原理是将枚举序数查找数组替换为包含全零的数组,这些数组将属于默认情况。

 // 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 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 entry : changedFields.entrySet()) { try { entry.getKey().set(null, entry.getValue()); } catch (Exception ex) { ex.printStackTrace(System.err); } } } 

Mockito不支持Mockito枚举值,但是powermock确实如此。

尝试这个。

我已经创建了自己的类来模拟它们。 请映射到您自己的课程。

 @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); } } 

这是方法:

 public class BasicTrails { public static boolean find(Trail trail) { switch (trail) { case YES: return true; case NO: return false; default: throw new RuntimeException("Invalid"); } } 

这是枚举

 public enum Trail { YES, NO; } 

在Powermock的帮助下,我们可以实现这一点,因为Powermock支持模拟最终类

 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); } } } 

您可以模拟,破解或尝试使其工作,但有很简单的方法如何做到这一点。 我假设您正在使用maven或gradle,因此您拥有maintest配置文件。

然后在主配置文件中,您有如上代码:

 package my.cool.package; public enum TestEnum { YES, NO } 

但是在测试配置文件中你可以有另一个:

 // EXACTLY SAME as above package my.cool.package; public enum TestEnum { YES, NO, INVALID_FOR_TEST_ONLY } 

现在你可以在测试中使用新值INVALID_FOR_TEST_ONLY ,它在prod配置文件中不可用。

有两个缺点:

  • 如果你更新prod enum,你可能还需要更新测试(如果你想测试那么)
  • 某些IDE可能无法正常使用此技巧,即使maven理解得很好