Skip to content

Mockito fails to mock non-public inner class in continuous testing due to classloading issues #38987

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

Closed
famod opened this issue Feb 23, 2024 · 13 comments · Fixed by #34681
Closed

Comments

@famod
Copy link
Member

famod commented Feb 23, 2024

Describe the bug

The following primitive test (and tested class):

@ExtendWith(MockitoExtension.class)
public class FooTest {

    @Mock
    private Foo.Inner mock;

    @Test
    public void test() {

    }
}
public class Foo {

    static class Inner {
    }
}

works fine in IDE or mvn, but fails with mvn quarkus:test:

ERROR [io.qua.test] (Test runner thread) Test FooTest#test() failed 
: org.mockito.exceptions.base.MockitoException: 
Mockito cannot mock this class: class org.acme.Foo$Inner.

Mockito can only mock non-private & non-final classes, but the root cause of this error might be different.
Please check the full stacktrace to understand what the issue is.
If you're still not sure why you're getting this error, please open an issue on GitHub.


Java               : 17
JVM vendor name    : Azul Systems, Inc.
JVM vendor version : 17.0.9+8-LTS
JVM name           : OpenJDK 64-Bit Server VM
JVM version        : 17.0.9+8-LTS
JVM info           : mixed mode, emulated-client, sharing
OS name            : Linux
OS version         : 5.15.0-94-generic


Underlying exception : org.mockito.exceptions.base.MockitoException: 
Cannot create mock for class org.acme.Foo$Inner

The type is not public and its mock class is loaded by a different class loader.
This can have multiple reasons:
 - You are mocking a class with additional interfaces of another class loader
 - Mockito is loaded by a different class loader than the mocked type (e.g. with OSGi)
 - The thread's context class loader is different than the mock's class loader
	at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:160)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	Suppressed: java.lang.NullPointerException: Cannot invoke "java.util.Set.forEach(java.util.function.Consumer)" because the return value of "org.junit.jupiter.api.extension.ExtensionContext$Store.remove(Object, java.lang.Class)" is null
		at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:194)
		... 2 more
Caused by: org.mockito.exceptions.base.MockitoException: 
Cannot create mock for class org.acme.Foo$Inner

The type is not public and its mock class is loaded by a different class loader.
This can have multiple reasons:
 - You are mocking a class with additional interfaces of another class loader
 - Mockito is loaded by a different class loader than the mocked type (e.g. with OSGi)
 - The thread's context class loader is different than the mock's class loader
	at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:168)
	at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:399)
	at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:190)
	at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:410)
	... 3 more


ERROR [io.qua.test] (Test runner thread) >>>>>>>>>>>>>>>>>>>> Summary: <<<<<<<<<<<<<<<<<<<<
FooTest#test()

Expected behavior

No failure, should work as in IDE or mvn.

Actual behavior

Fails with classloading issue.

How to Reproduce?

  1. clone https://github.com/famod/q_ctest-inner
  2. mvn clean verify (passes)
  3. mvn quarkus:test fails

Output of uname -a or ver

No response

Output of java -version

No response

Quarkus version or git rev

3.7.4

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

The problem vanishes after adding public to the inner class.

I have another case where adding public helps, but that's not an inner class.

It has been an issue for many releases now, I just haven't found the time to report it.
I'm rather sure (IIRC) it was actually working some months ago.

Copy link

quarkus-bot bot commented Feb 23, 2024

/cc @geoand (testing), @stuartwdouglas (continuous-testing)

@famod
Copy link
Member Author

famod commented Feb 23, 2024

I just found out it worked with 3.1.3.Final and broke with 3.2.0.CR1.

@famod
Copy link
Member Author

famod commented Feb 23, 2024

Reverting 13103ee fixes it!
So for this to work, the TCCL has to be set before every test.

It also fixes my non-inner class case (which had other issues after adding public).

/cc @stuartwdouglas

@famod
Copy link
Member Author

famod commented Feb 26, 2024

The mentioned commit has this note:

This prevents extensions from modifying the TCCL. The correct TCCL is set at the start of the run.

I'm wondering whether we had any concrete issues with specific extensions. The PR doesn't link anything in that regard.

/cc @geoand
Also /cc @holly-cummins, I've seen you fighting various test classloading related issues in the past. 🙂

@geoand
Copy link
Contributor

geoand commented Feb 27, 2024

I'm wondering whether we had any concrete issues with specific extensions. The PR doesn't link anything in that regard.

Yeah, I am wondering the same...

@famod
Copy link
Member Author

famod commented Mar 8, 2024

FWIW, reverting causes a couple of test failures in ComponentContinuousTestingTest (which I haven't looked at in detail yet).

@famod
Copy link
Member Author

famod commented Mar 11, 2024

FWIW, reverting causes a couple of test failures in ComponentContinuousTestingTest (which I haven't looked at in detail yet).

I think this is the root cause of that test failure (after the revert):

java.lang.IllegalArgumentException: Annotation is not a registered qualifier: interface jakarta.enterprise.inject.Any
 	at io.quarkus.arc.impl.Qualifiers.verifyQualifier(Qualifiers.java:152)
 	at io.quarkus.arc.impl.Qualifiers.verify(Qualifiers.java:47)
 	at io.quarkus.arc.impl.ArcContainerImpl.resolveObserverMethods(ArcContainerImpl.java:881)
 	at io.quarkus.arc.impl.EventImpl.createNotifier(EventImpl.java:182)
 	at io.quarkus.arc.impl.ArcContainerImpl.notifierOrNull(ArcContainerImpl.java:513)
 	at io.quarkus.arc.impl.ArcContainerImpl.<init>(ArcContainerImpl.java:206)
 	at io.quarkus.arc.Arc.initialize(Arc.java:38)
 	at io.quarkus.arc.Arc.initialize(Arc.java:22)
 	at io.quarkus.test.component.QuarkusComponentTestExtension.startContainer(QuarkusComponentTestExtension.java:401)
 	at io.quarkus.test.component.QuarkusComponentTestExtension.beforeEach(QuarkusComponentTestExtension.java:231)
 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

/cc @mkouba

@mkouba
Copy link
Contributor

mkouba commented Mar 14, 2024

FWIW, reverting causes a couple of test failures in ComponentContinuousTestingTest (which I haven't looked at in detail yet).

I think this is the root cause of that test failure (after the revert):

java.lang.IllegalArgumentException: Annotation is not a registered qualifier: interface jakarta.enterprise.inject.Any
 	at io.quarkus.arc.impl.Qualifiers.verifyQualifier(Qualifiers.java:152)
 	at io.quarkus.arc.impl.Qualifiers.verify(Qualifiers.java:47)
 	at io.quarkus.arc.impl.ArcContainerImpl.resolveObserverMethods(ArcContainerImpl.java:881)
 	at io.quarkus.arc.impl.EventImpl.createNotifier(EventImpl.java:182)
 	at io.quarkus.arc.impl.ArcContainerImpl.notifierOrNull(ArcContainerImpl.java:513)
 	at io.quarkus.arc.impl.ArcContainerImpl.<init>(ArcContainerImpl.java:206)
 	at io.quarkus.arc.Arc.initialize(Arc.java:38)
 	at io.quarkus.arc.Arc.initialize(Arc.java:22)
 	at io.quarkus.test.component.QuarkusComponentTestExtension.startContainer(QuarkusComponentTestExtension.java:401)
 	at io.quarkus.test.component.QuarkusComponentTestExtension.beforeEach(QuarkusComponentTestExtension.java:231)
 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

/cc @mkouba

Well, I don't think there's something we could fix in ArC or in QuarkusComponentTest 🤷.

@famod
Copy link
Member Author

famod commented Sep 6, 2024

Hi @holly-cummins, this might be something for WG - Test classloading

@geoand
Copy link
Contributor

geoand commented Feb 18, 2025

Is this still an issue with Quarkus main ?

@holly-cummins
Copy link
Contributor

I've confirmed that this still fails with 3.18.1, but passes with my WG - Test classloading branch. (Yay!).

We should perhaps add a test with the reproducer. I have mixed feelings since every test we add slows down the build, but I think we need to have more coverage of some of these continuous testing scenarios.

@holly-cummins holly-cummins removed the triage/needs-feedback We are waiting for feedback. label Feb 18, 2025
@geoand
Copy link
Contributor

geoand commented Feb 18, 2025

+1 for adding tests of cases that were known to not work but your PR fixes

holly-cummins added a commit to holly-cummins/quarkus that referenced this issue Mar 13, 2025
holly-cummins added a commit to holly-cummins/quarkus that referenced this issue Mar 13, 2025
holly-cummins added a commit to holly-cummins/quarkus that referenced this issue Mar 13, 2025
holly-cummins added a commit to holly-cummins/quarkus that referenced this issue Mar 13, 2025
holly-cummins added a commit to holly-cummins/quarkus that referenced this issue Mar 13, 2025
holly-cummins added a commit to holly-cummins/quarkus that referenced this issue Mar 13, 2025
holly-cummins added a commit to holly-cummins/quarkus that referenced this issue Mar 14, 2025
@holly-cummins
Copy link
Contributor

Reproducer added in #46793.

@github-project-automation github-project-automation bot moved this from Todo to Done in WG - Test classloading Mar 25, 2025
@quarkus-bot quarkus-bot bot added this to the 3.22 - main milestone Mar 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging a pull request may close this issue.

4 participants