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