jmockit / jmockit1

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

java.lang.VerifyError when try to partially mock lambda expressions #215

Closed hamid-nazari closed 8 years ago

hamid-nazari commented 8 years ago

Hi, I've come across this issue which seem naive and simple to solve, but has become a pain for my tests. When I try to partially mock an inline implementation of any form of interface, especially those Functional Interfaces introduced with Java8, using Lambda expression I get an annoying java.lang.VerifyError like this:

java.lang.VerifyError
    at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at JMockitTest$4.<init>(JMockitTest.java:48)
    at JMockitTest.lambda_partial_mock_VerifyError(JMockitTest.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:497)

To my surprise, the same implementation using anonymous inline class gets partially mocked successfully. For my further amusement, I found that the Lambda body seems to play a role here; that is, using a local variable or a class filed in the same Lambda body has different effects for partial mocking. This is the test case to demonstrate this issue:

import static org.testng.Assert.assertEquals;

import java.util.ArrayList;
import java.util.List;
import java.util.function.IntSupplier;

import org.testng.annotations.Test;

import mockit.Expectations;

public class JMockitTest
{
    List<String> fieldList = new ArrayList<>();

    @Test
    public void lambda_partial_mock_VerifyError()
    {
        List<String> localList = fieldList;
        IntSupplier supplierLocalLambda = () -> localList.stream().mapToInt(c -> 1).sum();
        new Expectations(supplierLocalLambda) // Dynamic Partial Mocking: OK
        {
            {
                supplierLocalLambda.getAsInt();
                result = 178;
            }
        }.toString();
        assertEquals(supplierLocalLambda.getAsInt(), 178); // PASS

        IntSupplier supplierFieldAnon = new IntSupplier()
        {

            @Override
            public int getAsInt()
            {
                return fieldList.stream().mapToInt(c -> 1).sum();
            }
        };
        new Expectations(supplierFieldAnon) // Dynamic Partial Mocking: OK
        {
            {
                supplierFieldAnon.getAsInt();
                result = 80;
            }
        }.toString();
        assertEquals(supplierFieldAnon.getAsInt(), 80); // PASS

        IntSupplier supplierFieldLambda = () -> fieldList.stream().mapToInt(c -> 1).sum();
        new Expectations(supplierFieldLambda) // Dynamic Partial Mocking: java.lang.VerifyError
        {
            {
                supplierFieldLambda.getAsInt();
                result = -200;
            }
        }.toString();
        assertEquals(supplierFieldLambda.getAsInt(), -200); // Never reaches here
    }
}

The test fails with the error I mentioned earlier at the line I've commented. I check this with both versions JMockit 1.16 and JMockit 1.19.

My environment:

rliesenfeld commented 8 years ago

Lambda bodies are implemented differently from anything else in the JVM. For now, they are not supported.

Out of curiosity, is there a real-world use for this? What kind of test would need to mock a lambda?

hamid-nazari commented 8 years ago

This test code snippet has nothing to do with the actual code I encountered this issue with. In that scenario we have a set of dependencies (like hooks) defined as functional interfaces and of course implemented as lambdas and I use JMockit to verify and track my code proper interaction with those hooks. Specifically, I want to verify these hooks invocation count for specific set of input arguments from within my code which is pretty much complex and distributed. I suppose such use-cases justify this requirement according to the behavioral testing paradigm.