jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
461 stars 239 forks source link

Multiple implementations of an Interface using Anonymous Class implementations #174

Closed marcfarrow closed 9 years ago

marcfarrow commented 9 years ago

We have an internal library in which parts of the library implement a global interface. Sometimes, the implementation of this interface is an anonymous class. We are trying to partially mock all the interface implementations for that interface definition. We want to create these "MockUp"s in an external class for testing purposes so we can manipulate the call to the method to "change" the value returned. We don't just want a new value returned, we want to physically add information to the existing call results. I have created a full testing package that uses jMockit 1.17 and jUnit 1.4 with an attempt to achieve this goal. It is very likely I am missing something small or that I have found a bug. There are two test cases in the unit test. The first case fails because the real value and the mocked value are the same which denotes that the interface implementations are not being mocked. The second case fails with exception. This second case is the real issue where I think there might be a bug. the instantiation of the mocked objects successfully mocks all interfaces. However, when the second implementation runs through the mocked code, an IllegalArgumentException (full stack trace will be provided) is thrown. It is my opinion that the "mocked" code was originally instantiated and loaded. The second time the "mocked" code is supposed to be ran it using the same instantiation which is related to TestClass1$1 instead of TestClass2$1. This can be seen by reviewing the Exception.getMessage() of the of the stack trace (which states TestClass1$1) versus the TestClass2$1 in the stack trace elements. Any thoughts or ideas would be greatly appreciated.

Test1 Results:

1
2
1
2
java.lang.AssertionError: Test value versus mocked value should not be the same for test 1
    at org.junit.Assert.fail(Assert.java:91)
    at org.junit.Assert.assertTrue(Assert.java:43)
    at org.junit.Assert.assertFalse(Assert.java:68)
    at test.cases.JUnitTestCase.testMockingWithoutGenerics(JUnitTestCase.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at junit.framework.JUnit4TestAdapter.run(JUnit4TestAdapter.java:39)
    at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.run(JUnitTestRunner.java:518)
    at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.launch(JUnitTestRunner.java:1052)
    at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.main(JUnitTestRunner.java:906)

Test2 Results:

1
2
MOCKED DATA: 1
java.lang.IllegalArgumentException: Failure to invoke method: public java.lang.String test.cases.TestClass1$1.getData()
    at mockit.internal.util.MethodReflection.invoke(MethodReflection.java:101)
    at mockit.internal.BaseInvocation.doProceed(BaseInvocation.java:68)
    at mockit.Invocation.proceed(Invocation.java:143)
    at test.cases.MockTestInterface2.getData(MockTestInterface2.java:14)
    at test.cases.TestClass2$1.getData(TestClass2.java)
    at test.cases.JUnitTestCase.testMockingWithGenerics(JUnitTestCase.java:69)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at junit.framework.JUnit4TestAdapter.run(JUnit4TestAdapter.java:39)
    at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.run(JUnitTestRunner.java:518)
    at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.launch(JUnitTestRunner.java:1052)
    at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.main(JUnitTestRunner.java:906)
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at test.cases.MockTestInterface2.getData(MockTestInterface2.java:14)
    at test.cases.TestClass2$1.getData(TestClass2.java)
    at test.cases.JUnitTestCase.testMockingWithGenerics(JUnitTestCase.java:69)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:597)
    ... 4 more

TestInterface.java

package test.cases;

/**
 * @since May 12, 2015 8:41:20 AM
 */
public interface TestInterface {

    public String getData();

}

TestClass1.java

package test.cases;

/**
 * @since May 12, 2015 8:40:22 AM
 */
public class TestClass1 {

    public final static TestInterface INTER = new TestInterface() {

        @Override
        public String getData() {
            return "1";
        }
    };
}

TestClass2.java

package test.cases;

/**
 * @since May 12, 2015 8:40:22 AM
 */
public class TestClass2 {

    public final static TestInterface INTER = new TestInterface() {

        @Override
        public String getData() {
            return "2";
        }

    };
}

MockTestInterface1.java

package test.cases;

import mockit.Invocation;
import mockit.Mock;
import mockit.MockUp;

/**
 * @since May 12, 2015 8:44:14 AM
 */
public class MockTestInterface1 extends MockUp<TestInterface> {

    @Mock
    public String getData(final Invocation invoke) {
        return "MOCKED DATA: " + invoke.proceed();
    }
}

MockTestInterface2.java

package test.cases;

import mockit.Invocation;
import mockit.Mock;
import mockit.MockUp;

/**
 * @since May 12, 2015 8:44:14 AM
 */
public class MockTestInterface2<T extends TestInterface> extends MockUp<T> {

    @Mock
    public String getData(final Invocation invoke) {
        return "MOCKED DATA: " + invoke.proceed();
    }
}

JUnitTestCase.java

package test.cases;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

/**
 * @since May 12, 2015 8:48:16 AM
 */
public class JUnitTestCase {

    public JUnitTestCase() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
    }

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testMockingWithoutGenerics() {
        try {
            /* First create instance of each test Class with static field to anonymous
             * and print the data.
             */
            final String test1Value = TestClass1.INTER.getData();
            System.out.println(test1Value);
            final String test2Value = TestClass2.INTER.getData();
            System.out.println(test2Value);
            /* Instantiate mocked class to load into class loader */
            new MockTestInterface1();
            final String mocked1Value = TestClass1.INTER.getData();
            System.out.println(mocked1Value);
            final String mocked2Value = TestClass2.INTER.getData();
            System.out.println(mocked2Value);
            assertFalse("Test value versus mocked value should not be the same for test 1", test1Value.equals(mocked1Value));
            assertFalse("Test value versus mocked value should not be the same for test 2", test1Value.equals(mocked1Value));
        } catch (final Throwable t) {
            t.printStackTrace();
            fail(t.toString());
        }
    }

    @Test
    public void testMockingWithGenerics() {
        try {
            /* First create instance of each test Class with static field to anonymous
             * and print the data.
             */
            final String test1Value = TestClass1.INTER.getData();
            System.out.println(test1Value);
            final String test2Value = TestClass2.INTER.getData();
            System.out.println(test2Value);
            /* Instantiate mocked class to load into class loader */
            new MockTestInterface2();
            final String mocked1Value = TestClass1.INTER.getData();
            System.out.println(mocked1Value);
            final String mocked2Value = TestClass2.INTER.getData();
            System.out.println(mocked2Value);
            assertFalse("Test value versus mocked value should not be the same for test 1", test1Value.equals(mocked1Value));
            assertFalse("Test value versus mocked value should not be the same for test 2", test1Value.equals(mocked1Value));
        } catch (final Throwable t) {
            t.printStackTrace();
            fail(t.toString());
        }
    }
}
rliesenfeld commented 9 years ago

Thanks for reporting; seems the second class implemeting the interface is not getting mocked. A simpler version of the test follows.

public interface TestInterface { String getData(); }    
static final TestInterface INTER1 = new TestInterface() { @Override public String getData() { return "1"; } };
static final TestInterface INTER2 = new TestInterface() { @Override public String getData() { return "2"; } };

public static class MockTestInterface<T extends TestInterface> extends MockUp<T> {
    @Mock
    public String getData(Invocation invoke) { return "MOCKED DATA: " + invoke.proceed(); }
}

@Test
public void mockAllClassesImplementingAnInterface() {
    String test1Value = INTER1.getData();
    String test2Value = INTER2.getData();

    new MockTestInterface();

    String mocked1Value = INTER1.getData();
    String mocked2Value = INTER2.getData();

    assertNotEquals(test1Value, mocked1Value);
    assertNotEquals(test2Value, mocked2Value);
}