jmockit / jmockit1

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

State associated with @Tested class + @Inject constructor doesn't appear to be cleaned up between test classes #258

Closed 6bangs closed 8 years ago

6bangs commented 8 years ago

Minimum example:

@RunWith(Suite.class)
@Suite.SuiteClasses({ Test1.class, Test2.class })
public class TwoTests {}

public class A {
    @Inject
    public A(Double dep) {}

    public void doSomething(Boolean parameter) {}
}

public class Test1 {
    @Tested A a;
    @Injectable Double dep;

    @Test
    public void testOne() throws Exception {}
}

public class Test2 {
    @Mocked A a;

    @Test
    public void testTwo() throws Exception {
        new Verifications() {{
            a.doSomething(anyBoolean); times = 0;
        }};
    }
}

If Test1 is run before Test2, an NPE is raised with the following stack trace:

java.lang.NullPointerException
    at mockit.internal.state.ParameterNames.getName(ParameterNames.java:107)
    at mockit.internal.util.MethodFormatter.appendParameterName(MethodFormatter.java:137)
    at mockit.internal.util.MethodFormatter.appendFriendlyTypes(MethodFormatter.java:112)
    at mockit.internal.util.MethodFormatter.appendFriendlyMethodSignature(MethodFormatter.java:79)
    at mockit.internal.util.MethodFormatter.<init>(MethodFormatter.java:37)
    at mockit.internal.expectations.invocation.InvocationArguments.toString(InvocationArguments.java:144)
    at mockit.internal.expectations.invocation.ExpectedInvocation.toString(ExpectedInvocation.java:344)
    at java.lang.String.valueOf(String.java:2849)
    at java.lang.StringBuilder.append(StringBuilder.java:128)
    at mockit.internal.expectations.invocation.ExpectedInvocation.errorForMissingInvocation(ExpectedInvocation.java:260)
    at mockit.internal.expectations.BaseVerificationPhase.handleInvocation(BaseVerificationPhase.java:96)
    at mockit.internal.expectations.RecordAndReplayExecution.recordOrReplay(RecordAndReplayExecution.java:227)
    at A.doSomething(A.java)
    at Test2$1.<init>(Test2.java:19)
    at Test2.testTwo(Test2.java:18)
...

However, if Test2 is run first, both tests pass.

JMockit version: 1.21 JUnit version: 4.11

rliesenfeld commented 8 years ago

Thanks! It's a bug indeed, and not limited to multiple tests; the simplified test below also reproduces it:

public class ParameterNameExtractionTest
{
    static class A
    {
        A() {}
        A(int i) {}
        void doSomething(boolean b) {}
    }

    @Tested A a1; // causes parameter name extraction for class A

    @Test
    public void mockClassPreviouslyUsedInTestedField(@Mocked final A a2)
    {
        new Verifications() {{
            a2.doSomething(true); // NPE here, as extraction missed "doSomething(boolean)"
            times = 0;
        }};
    }
}
6bangs commented 8 years ago

Ahh, glad to get a peek at what's going on under the covers :)

I debugged far enough to understand that @Tested caused parameter name extraction, but didn't have time yesterday to dig deeper. Looking forward to reading the fix!