JMockit 无法模拟来自 JRE 的具体集合

JMockit can't mock concrete collection from JRE

我有需要模拟 HashMap 的测试用例 class 但 JMockit 在模拟它时似乎遇到了困难。 以下代码在 Expectations 块中触发 NPE:

public class TestKO {

public class ClassUnderTest {

    private final Map<String, String> dependency;

    public ClassUnderTest() {
        this.dependency = new HashMap<String, String>();
    }

    public void register(final String key, final String value) {
        dependency.put(key, value);
    }
}

@Test
public void ko(@Mocked("put") final HashMap<String, String> dep) {
    new Expectations() {{
            dep.put("key", "value"); // dep is null => NullPointerException !
        }};
    final ClassUnderTest c = new ClassUnderTest();
    c.register("key", "value");
}

}

如果我用任何 POJO 替换 HashMap,JMockIt 可以模拟它并且测试会成功。 事实上,JMockit 似乎无法从 Java 集合框架中模拟任何具体 class(也无法 class 派生出任何具体 class)。

以下测试用例说明了这个问题:

public class MyTests {

public static class POJO {
}

public static class MyMapExtendingConcreteCollection extends HashMap<String, String> {
}

public static class MyMapExtendingAbstractCollection extends AbstractMap<String, String> {

    @Override
    public Set<java.util.Map.Entry<String, String>> entrySet() {
        return null;
    }

}

@Test
public void strangeBehaviors(@Mocked POJO mockedPOJO, @Mocked HashMap<String, String> mockedMap,
        @Mocked ConcurrentHashMap<String, String> mockedConcurrentMap,
        @Mocked MyMapExtendingConcreteCollection mockedMyMapFromConcrete,
        @Mocked MyMapExtendingAbstractCollection mockedMyMapFromAbstract) {
    assertNotNull(mockedPOJO); // OK : not null
    assertNotNull(mockedMap); // FAIL: null !
    assertNotNull(mockedConcurrentMap); // OK : not null
    assertNotNull(mockedMyMapFromConcrete); // FAIL: null !
    assertNotNull(mockedMyMapFromAbstract); // OK: not null
}

}

我会不会在使用 JMockit 时产生误解?

感谢您的帮助。

由于 JMockit 中的错误,mock 参数为 null,如果它们恰好被模拟,则会影响某些广泛使用的 JRE 类(包括 ArrayList、HashMap 和其他一些)。

错误可以修复,但这里真正的问题是是否应该首先允许模拟这样的 类。在我看来,他们不应该,因为几乎可以肯定没有合法的用例。

在问题中暴露的情况下,测试应该: a) 通过 public getter 或某些其他方法的 return 值验证某些内部 collection/map 的状态; b) 或者,作为最后的手段,使用反射来获取对内部状态的访问(mockit.Deencapsulation 可以提供帮助)。

同样的规则应该适用于 sub类 of AbstractCollection, HashMap, 等等,因为他们也只是数据持有者不应嘲笑这一点。更一般地说,没有 java.util.* 接口方法应该是可模拟的(除了少数例外)。在未来的版本中,JMockit 将在尝试使用 Expectations API.

模拟此类方法时抛出描述性异常。

归根结底,无论是因为该工具无法正确模拟它,还是它不会选择模拟它,如果不能编写此类测试,对用户来说是最好的。 "Value object" 类 和 collections/maps 根本不应该被嘲笑;总是有更好的方法来编写测试。

ArrayList、Hashmap 等对象无需模拟,因为我们可以使用虚拟对象进行测试。

而不是模拟列表

@Mock List<Object>;

使用虚拟对象来提供输入

 public List<Object> get(){
  List<Object> list=new ArrayList<Object>();
  list.add(new Customer());
  return list;
  } 

在被测试对象有一个列表作为构造函数参数的情况下,能够用@Injectable 注释列表会很有帮助,例如

    @Injectable
    List<Path> testList = new ArrayList<Path>();

    @Tested
    ObjectWithListAsConstructorParameter myTestedObject;

这样的测试失败 java.lang.IllegalArgumentException: java.util.List is not mockable。解决方法是在每次测试之前创建测试对象。但是,使用 @Tested 和 @Injectable 注释来执行此操作会更好。