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 注释来执行此操作会更好。
我有需要模拟 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 注释来执行此操作会更好。