1
votes

NOTE: fully working code snippet follows at end of the question

I have a JMockIt test for a class Service with two dependencies (Factory and Consumer in the snippet). The service internally asks the Factory dependency for an object of class X, then delivers that object to the Consumer dependency in this method:

public void interact(int i, E e) { X v = f.create(i, e); c.consume(v); }

In my test, I want to exercise the Service.interact() method and postulate through a strict expectation that the consumer shall receive a parameter value that satisfies specific properties. Now, since the parameter of type X is a POJO that doesn't implement equals(), I want to use its provided getter methods to perform my checks; X looks like this (full snippet follows at end of question):

public static class X { private final int i; public final E e; // ... see below for full snippet public int getI() { return i; } public E getE() { return e; } // E is an enum

So what I do is I try to check that the consumer receives a value with X.getI() == 2 and X.getE() == E.B and I do this using both the with(Delegate<X>) and withArgThat(Matcher<X>) in my expectations block:

new StrictExpectations() {{ consumer.consume(withArgThat(new BaseMatcher() { // ... // and new StrictExpectations() {{ consumer.consume(with(new Delegate() { // ...

However, both checks fail because when they call X.getE() they receive something that is NOT the enumeration value E.B. The output of the unit test is:

-----------------------------------------
RUNNING CHECKS ON: i=2, e=B
Direct reference match.
Reference through getter mismatch!
Ordinal through getter reference: 0
Identity hashcode from getter reference: 1282788025
Identity hashcode of expected reference: 519569038
-----------------------------------------
RUNNING CHECKS ON: i=2, e=B
Direct reference match.
Reference through getter mismatch!
Ordinal through getter reference: 0
Identity hashcode from getter reference: 1911728085
Identity hashcode of expected reference: 519569038

First question is (the title of this post): why does JMockIt intervene with this getter in the first place? As far as I can tell, I have not asked it to mock class X so invocations to its getter methods should go into regular X bytecode, but apparently it does not; the ordinal of the returned enumeration value is zero, which seems like a default value for a mock. Is this expected behavior because I've missed/misunderstood something, or is it maybe a bug?

Second question is: assuming there is a valid reason for JMockIt's behavior, how can I achieve what I want and perform this kind of check on values that I can only obtain through X's getter methods? How can I fix this code to tell JMockIt to simply leave class X alone.

Here is the full snippet that will compile and run (also at http://pastebin.com/YRL7Pdzv), I have junit-4.12, hamcrest-core-1.3 and jmockit-1.14 in my classpath, and using Oracle JDK 1.7.0_51.

package demonstrate;

import mockit.Delegate;
import mockit.Mocked;
import mockit.NonStrictExpectations;
import mockit.StrictExpectations;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;

public class SomeTest {
    @Mocked
    Factory factory;
    @Mocked
    Consumer consumer;

    @Before
    public void setUp() {
        new NonStrictExpectations() {{
            factory.create(anyInt, (E) any);
            result = new Delegate() {
                @SuppressWarnings("unused")
                public X create(int i, E e) {
                    return new X(i, e);
                }
            };
        }};
    }

    @Test
    public void testWithMatcher() {
        System.err.println("-----------------------------------------");
        new StrictExpectations() {{
            consumer.consume(withArgThat(new BaseMatcher() {
                final int i = 2; final E e = E.B;
                @Override
                public boolean matches(Object item) {
                    return runChecks((X) item, i, e);
                }
                @Override
                public void describeTo(Description description) {
                    description.appendText("\"i=" + i + ", e=" + e.name() + "\"");
                }
            }));
        }};
        new Service(factory, consumer).interact(2, E.B);
    }

    @Test
    public void testWithDelegate() {
        System.err.println("-----------------------------------------");
        new StrictExpectations() {{
            consumer.consume(with(new Delegate() {
                @SuppressWarnings("unused")
                public boolean check(X x) {
                    return runChecks(x, 2, E.B);
                }
            }));
        }};
        new Service(factory, consumer).interact(2, E.B);
    }

    private static boolean runChecks(X actual, int i, E e) {
        System.err.println("RUNNING CHECKS ON: " + actual);
        if (actual.getI() != i) {
            System.err.println("Primitive int mismatch!");
            return false;
        }
        if (actual.e == e) {
            System.err.println("Direct reference match.");
        } else {
            System.err.println("Reference mismatch!");
            return false;
        }
        E otherE = actual.getE();
        if (otherE != e) {
            System.err.println("Reference through getter mismatch!");
            System.err.println("Ordinal through getter reference: "
                    + otherE.ordinal());
            System.err.println("Identity hashcode from getter reference: "
                    + System.identityHashCode(otherE));
            System.err.println("Identity hashcode of expected reference: "
                    + System.identityHashCode(e));
            return false;
        }
        return true;
    }

    public enum E {
        A, B
    }

    public static class X {
        private final int i;
        public final E e;

        public X(int i, E e) {
            this.i = i;
            this.e = e;
        }

        @Override
        public String toString() {
            return "i=" + i + ", e=" + e.name();
        }

        public int getI() {
            return i;
        }

        public E getE() {
            return e;
        }
    }

    public static class Factory {
        public X create(int i, E e) {
            return new X(i, e);
        }
    }

    public static class Consumer {
        public void consume(X arg) {
        }
    }

    public static class Service {
        private final Factory f;
        private final Consumer c;

        public Service(Factory f, Consumer c) {
            super();
            this.f = f;
            this.c = c;
        }

        public void interact(int i, E e) {
            X v = f.create(i, e);
            c.consume(v);
        }
    }
}
1

1 Answers

1
votes

I was told in the jmockit-users list that this is actually a bug.

The suggested workaround was to use @Mocked(cascading = false) Factory factory; and indeed it makes things work.