对于不存在的枚举值的单元测试?

时间:2014-09-19 08:17:01

标签: java junit mockito

首先是一些示例代码......

枚举:

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是我从不同开发人员的不同代码导入的东西。所以它实际上可以改变。对于这种情况,我想要一个实际检查该非现有值的单元测试。但我根本不知道如何使用Mockito和JUnit。

这部分当然不起作用:

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

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

有什么想法吗?

5 个答案:

答案 0 :(得分:6)

如何简单:

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

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

答案 1 :(得分:6)

基于@assylias的回答,我认为这是你能做的最好的事情:

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

由于enum转换语句的编译方式,在switch语句中伪造不存在的enum值是不可能的(AFAIK)。即使您通过反射摆弄ordinal实例中的内部enum字段,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<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);
    }
  }
}

答案 2 :(得分:4)

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

答案 3 :(得分:1)

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


    }
}

答案 4 :(得分:0)

您可以模拟,破解或尝试使其工作,但有很简单的方法如何做到这一点。我假设您正在使用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
}

现在您可以在test中使用新值INVALID_FOR_TEST_ONLY,但它无法在prod配置文件中使用。

有两个缺点:

  • 如果您更新prod enum,您可能还需要更新测试(如果您想进行测试)
  • 某些IDE可能无法正常使用此技巧,即使maven理解得很好
相关问题