javaunit-testingjmockit

JMockit can't mock concrete collection from JRE


I have test cases that require to mock HashMap class but it appears that JMockit encounters difficulties when mocking it. The following code trigger a NPE in the Expectations block :

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

}

If I replace the HashMap with any POJO, JMockIt can mock it and the test will succeed. In fact, it appears that JMockit can't mock any concrete class (nor class that derive that any concrete class) from the Java Collection Framework.

The following test case show this problem:

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
}

}

Will I misunderstand something in using JMockit ?

Thanks for your help.


Solution

  • The mock parameter is null because of a bug in JMockit, affecting certain widely-used JRE classes (including ArrayList, HashMap, and a few others) if they happen to be mocked.

    The bug could be fixed, but the real question here is whether such classes should be allowed to be mocked in the first place. In my opinion, they should not, as there is almost certainly no legitimate use cases.

    In the cases exposed in the question, the test should either: a) verify the state of some internal collection/map through public getters or the return value of some other method; b) or, as a last resort, use Reflection to obtain access to internal state (mockit.Deencapsulation can help here).

    The same rule should apply to subclasses of AbstractCollection, HashMap, etc., as they too are merely data holders that should not be mocked. More generally, no java.util.* interface method should be mockable (with a few exceptions). In a future release, JMockit will throw descriptive exceptions on attempts to mock such methods with the Expectations API.

    Bottom-line, whether because the tool fails to mock it properly, or it won't mock it by choice, it's best for the user if such tests cannot be written. "Value object" classes and collections/maps simply should not be mocked; there are always better ways to write the test.