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

问题描述 投票:0回答:6

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

枚举:

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 一起工作。

有什么想法吗?

java junit mockito
6个回答
8
投票

简单一点怎么样:

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,因为顺序并不重要)


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 语句的编译方式,不可能(据我所知)在
switch
语句中伪造不存在的
enum
值。 即使您通过反射来摆弄
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);
    }
  }
}

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

2
投票

你可以模拟、破解或尝试让它工作,但有一个非常简单的方法可以做到这一点。我假设您正在使用 Maven 或 gradle,因此您有

main
test
配置文件。

然后在主配置文件中你有上面的代码:

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,并且它将在产品配置文件中不可用。

有两个缺点:

  • 如果您更新产品枚举,您可能还需要更新测试(如果您想要测试,那么)
  • 即使 Maven 很好理解,某些 IDE 也可能无法正确使用此技巧

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


    }
}

0
投票

如果需要测试使用枚举的 switch 语句中的默认情况,可以通过使用 Mockito 创建枚举的模拟来实现。该模拟可以模拟实际枚举中不存在的枚举值,从而强制开关达到默认情况。

这是分步指南:

逐步解决方案:

  1. 为不支持的枚举值创建模拟

使用 Mockito 创建枚举的模拟。该模拟将表示不支持的值,该值不属于实际枚举。

  1. 模拟values()方法:

使用Mockito中的MockedStatic来模拟枚举的values()方法。这允许您将模拟枚举值包含在 value() 返回的数组中,从而模拟意外枚举值的存在。

  1. 调用被测方法

如果您正在测试的方法是私有的或受保护的,请使用反射来调用它。这样,您可以直接将模拟枚举值传递给方法并强制 switch 语句命中默认情况。

  1. 断言预期异常:

使用assertThrows确保方法在遇到不支持的枚举值时抛出预期的异常。

示例代码

enum ExampleEnum {
    A, B, C, D
}

测试方法:

    @Test
    void testMethodWithUnsupportedEnum() throws NoSuchMethodException {
        try (MockedStatic<ExampleEnum> mockedStatic = Mockito.mockStatic(ExampleEnum.class)) {
            // Create a mock for the unsupported enum value
            ExampleEnum UNSUPPORTED = Mockito.mock(ExampleEnum.class);
            Mockito.when(UNSUPPORTED.ordinal()).thenReturn(99);  // Set a high ordinal value
            Mockito.when(UNSUPPORTED.name()).thenReturn("UNSUPPORTED");
            Mockito.when(UNSUPPORTED.toString()).thenReturn("UNSUPPORTED");

            // Mock the values() method to include the unsupported enum value
            mockedStatic.when(ExampleEnum::values)
                    .thenReturn(new ExampleEnum[]{
                            ExampleEnum.A,
                            ExampleEnum.B,
                            ExampleEnum.C,
                            ExampleEnum.D,
                            UNSUPPORTED
                    });

         // the params in getDeclaredMethod (): method name, and passe the param type in your method for exemple here our method have two params (String a, ExampleEnum exp)
          //use reflection to access the method if it's not public
              Method method = ExampleClass.class.getDeclaredMethod("methodUnderTest", String.class, ExampleEnum.class);
            method.setAccessible(true);

            // Assert that the default case throws the expected exception
            assertThrows(InvocationTargetException.class, () -> {
                method.invoke(null, "testValue", UNSUPPORTED);
            });
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.