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

Support for @InjectMock EntityManager #40807

Open
yrodiere opened this issue May 23, 2024 · 6 comments
Open

Support for @InjectMock EntityManager #40807

yrodiere opened this issue May 23, 2024 · 6 comments
Labels
area/hibernate-orm Hibernate ORM area/testing kind/enhancement New feature or request

Comments

@yrodiere
Copy link
Member

Description

While @InjectMock Session works fine to mock the Hibernate ORM session, oddly @InjectMock EntityManager doesn't, even if a given test only uses EntityManager (e.g. using Panache): any method you'll mock will fail because something expect you to use the Session return type, even though you're mocking a EntityManager object (see stracktrace near the bottom).

This is a bit counter-intuitive, so we should probably look into either making @InjectMock EntityManager work correctly, or documenting the limitation and adding clear exception messages when someone tries to do @InjectMock EntityManager ("@InjectMock doesn't work with EntityManager, use Session instead").

Stacktrace from: #40475 (comment)

...
[INFO] Running <redacted>.XXXRepositoryTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.619 s <<< FAILURE! -- in <redacted>.XXXRepositoryTest
[ERROR] <redacted>.XXXRepositoryTest.testPanacheMocking -- Time elapsed: 0.113 s <<< ERROR!
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:

Query$MockitoMock$wgzFLNsQ cannot be returned by createNativeQuery()
createNativeQuery() should return NativeQuery
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
   Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
   - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

        at <redacted>.XXXRepositoryTest.setup(XXXRepositoryTest.java:32)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:1013)
        at io.quarkus.test.junit.QuarkusTestExtension.interceptBeforeEachMethod(QuarkusTestExtension.java:808)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

2024-05-17 09:51:33,690 INFO  [io.quarkus] (main) <redacted> stopped in 0.107s
[INFO] 
[INFO] Results:
[INFO]
[ERROR] Errors: 
[ERROR]   XXXRepositoryTest.setup:32 WrongTypeOfReturnValue 
Query$MockitoMock$wgzFLNsQ cannot be returned by createNativeQuery()
createNativeQuery() should return NativeQuery
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
   Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies - 
   - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

[INFO]
[ERROR] Tests run: 14, Failures: 0, Errors: 1, Skipped: 0
...

PS: Doesn't work with doReturn(entityManager).when(solicitudRepository).getEntityManager(); neither.

Implementation ideas

No response

@geoand
Copy link
Contributor

geoand commented May 23, 2024

Oddly enough, I could not reproduce this problem in either hibernate-orm-quickstart or hibernate-orm-panache-quickstart.

For the former the test I added was:

@QuarkusTest
public class MockTest {

    @InjectMock
    EntityManager em;

    @Test
    public void test() {
        Fruit mockResponse = new Fruit();
        mockResponse.setId(1);
        mockResponse.setName("mock");
        Mockito.when(em.find(Fruit.class, 1)).thenReturn(mockResponse);

        Fruit fruit = em.find(Fruit.class, 1);
        Assertions.assertEquals("mock", fruit.getName());
    }
}

For the latter it was essentially the same thing but using FruitEntity instead of Fruit.

@yrodiere
Copy link
Member Author

@geoand I think that in order to reproduce this, you need to mock a method whose return type is overloaded in Session. Like createNativeQuery.

@geoand
Copy link
Contributor

geoand commented May 23, 2024

Oh, you are right, adding:

        Query mock = Mockito.mock(Query.class);
        Mockito.when(em.createNativeQuery("SELECT 1 FROM FRUIT")).thenReturn(mock);

surfaces the problem.

Honestly, I don't think we can do much...

@yrodiere
Copy link
Member Author

Yeah I don't know if we can solve this either, but we might be able to forbid @InjectMock EntityManager and provide a clear exception + document the limitation, at least.

@yrodiere
Copy link
Member Author

Or more generally forbid @InjectMock A where we know that matching CDI beans are exposed with type B extends A (so @InjectMock should use type B).

@geoand
Copy link
Contributor

geoand commented May 23, 2024

That's probably a good idea

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/hibernate-orm Hibernate ORM area/testing kind/enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants