Search code examples
javajmockit

JMockit consecutive expectations


I recently came across with the issue explained below and I could not find an explanation for it. My code under test looked OK but the test was failing. It took me a while to understand what was wrong especially as my code under test was not as simple as the one presented below.

I am sharing it with you guys with the hope to find some explanations and conclude whether it is a JMockit bug or desired behavior and consequently something to watch out when implementing our tests.

The comments in the code will show where the issue is.

Thank you in advance for your inputs.

package my.tst.pkg;

import mockit.Expectations;
import mockit.Mocked;
import org.testng.annotations.Test;

import java.util.Collection;

import static java.util.Arrays.asList;
import static org.testng.AssertJUnit.assertFalse;

public class ConsecutiveExpectationsTest {
    class KeyHolder{
        private String key;
        String getKey() {
            return key;
        }
    }
    class ClassUnderTest {
        private Collection<KeyHolder> keyHolders;

        ClassUnderTest(Collection<KeyHolder> keyHolders) {
            this.keyHolders = keyHolders;
        }
        boolean isValid() {
            // At least one holder with no key means invalid
            return !keyHolders.stream().filter(kh -> kh.getKey() == null).findFirst().isPresent();
        }
    }

    @Mocked
    KeyHolder keyHolder;

    @Test
    public void shouldBeInvalidIfNullKey() throws Exception {
        new Expectations() {{
            // This expectations fail the test
            keyHolder.getKey(); returns("KEY", null);
            // However if casting the null to a String the test will pass
            // Is this something we should always do?
            // keyHolder.getKey(); returns("KEY", (String) null);
        }};
        assertFalse(new ClassUnderTest(asList(keyHolder, keyHolder)).isValid());
    }
}

Solution

  • This is a case of ambiguous use of the Java language, which a (decent) Java IDE should report to the user.

    IntelliJ, for one, reports the "Confusing 'null' argument to var-arg method" warning.

    This is confusing/ambiguous because the second parameter of the returns(...) method is an Object... remainingValues varargs parameter (which in bytecode is just an array), and because of the way the Java compiler builds up the array argument for a call. If you simply pass null, the compiler interprets it as (Object[]) null, not as new Object[] {null} as you might expect. Casting it to String eliminates the ambiguity, forcing the compiler to produce an array of one null element.

    That said, JMockit could interpret a null Object[] argument as equivalent to the one-element array. This change may come in a future version; for now, the cast to a specific element type should be used (it's needed to avoid the code inspection warning anyway, for those using IntelliJ).