Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mockito.mock(...) does not work inside an Arquillian integration test anymore #3304

Closed
5 tasks done
ankit--sethi opened this issue Mar 22, 2024 · 1 comment
Closed
5 tasks done

Comments

@ankit--sethi
Copy link

ankit--sethi commented Mar 22, 2024

Check that

  • The mockito message in the stacktrace have useful information, but it didn't help
  • The problematic code (if that's possible) is copied here;
    Note that some configuration are impossible to mock via Mockito
  • Provide versions (mockito / jdk / os / any other relevant information)
  • Provide a Short, Self Contained, Correct (Compilable), Example of the issue
    (same as any question on stackoverflow.com)
  • Read the contributing guide

Versions
Mockito: 5.10.0
Junit Jupiter: 5.10.1
JDK: OpenJDK 21.0.2
OS: MacOS Sonoma 14.4
Wildfly: 26.1.3.Final
Arquillian: 1.8.0.Final

Reproducer Project
Instructions: run mvn clean install to see the error coming out of the ErrorReproducingTest class
mockito-arquillian-issue-demo.zip

Code

@Test
public void invokeMockitoInsideArquillianTest() {
    // This test is here to reproduce the error
    Mockito.mock(ArrayList.class);
}

Description
As part of an upgrade from Java 11 to 21, I was in the middle of bumping up Mockito from 3.12.4 to 5.10.0. My team uses Arquillian with a Managed Wildfly container to execute integration tests. Typically devs blend in a little bit of mocking here and there to avoid problematic code they don't want to actually execute.

After the upgrade, we see that executing Mockito.mock(Foo.class) inside the integration test, throws an error:

Caused by: java.lang.IllegalStateException: Failed to load interface org.mockito.plugins.MockMaker implementation declared in java.util.Collections$3@555ec655
...
Caused by: java.lang.NoClassDefFoundError: org/mockito/internal/creation/bytebuddy/inject/MockMethodDispatcher

I was able to get some background around this error from this older issue. I suspect that the key problem here is that Mockito ties to instantiate itself with a JBoss Classloader since it's inside the Wildfly container and not the bootstrap classloader as expected.

Note: the error reproducing project has an additional Mockito.mock(...); in the BaseArquillianTest.deployService(...) method which is responsible for packaging the WAR file and therefore runs "outside" the Wildfly container. No issues running it there!

We have a substantial body of integration tests which combine some usage of Mockito.mock(...) inside an Arquillian test and this is working at Mockito 3.12.4. It would be a substantial amount of refactoring if this is no longer possible to do with Mockito 5. I would appreciate if someone could come up with possible solution or workaround, thanks!

@ankit--sethi
Copy link
Author

ankit--sethi commented Apr 9, 2024

After much trial and error I was able to find a solution for this.

Short version is to add the following to your Arquillian deployment's jboss-deployment-structure.xml:

<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <system export="true">
                <paths>
                    <path name="org/mockito/internal/creation/bytebuddy/inject"/>
                </paths>
            </system>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

Long version:
These lines of code in InlineDelegateButeBuddyMockMaker.java are responsible for reading a file named MockMethodDispatcher.raw, renaming it to MockMethodDispatcher.class and adding it to the classpath for the Bootstrap Class Loader. This initialization is all done at runtime when the first attempt to use a Mock occurs.

When these events are occurring in the context of a Wildfly container, the first class loader encountered is a JBoss Class Loader, and NOT the bootstrap class loader. This classloader has no awareness of the new class file just added to the boostrap class loader's classpath and we get a NoClassDefFoundError.

I tried artificially adding the renamed MockMethodDispatcher.class file to my WAR deployment in the hopes that the JBoss class loaders would then be able to find the class. Unfortunately, this attempt is detected by Mockito and we get an error message that Mockito requires that class to be loaded by the boostrap class loader specifically! You can see this error message here.

What finally worked for me is this directive added to jboss-deployment-structure.xml. That section is usually used to allow Wildfly access to internal JDK packages (e.g. "sun/reflect") and I reasoned that any package mentioned here would trigger a delegation by the JBoss class loader to the bootstrap class loader. Which is precisely what happened and everything started to work again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant