Mocking Panache active record pattern not working
See original GitHub issueDescribe the bug
I’ve built a JAX-RS endpoint & a test using Panache & the active record pattern. I’m trying to mock Panache so I can test the resource class in isolation. It doesn’t seem to be working. I’ve successfully been able to do this using the repository pattern and am trying to achieve the same thing with the active record pattern. I’ve attached a reproducing project, but here are the details.
I also thought that void
methods on mocks do nothing by default (https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#doNothing--), so I tried eliminating the PanacheMock.doNothing().when(Fruit.class).persist();
but that ended up with the same result.
Changing to use RESTEasy classic results in the same error.
I’ve also posted this on Zulip: https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/Need.20help.20mocking.20with.20Panache.20using.20active.20record.20pattern
JAX-RS class:
@Path("/fruits")
public class FruitResource {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Blocking
@Transactional
public Fruit addFruit(@Valid Fruit fruit) {
fruit.persist();
return fruit;
}
}
Test class
@QuarkusTest
public class FruitResourceTests {
@Test
public void addFruit() {
PanacheMock.mock(Fruit.class);
PanacheMock.doNothing()
.when(Fruit.class).persist();
given()
.when()
.contentType(ContentType.JSON)
.body("{\"id\":1,\"name\":\"Grapefruit\",\"description\":\"Summer fruit\"}")
.post("/fruits")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body(
"id", is(1),
"name", is("Grapefruit"),
"description", is("Summer fruit")
);
PanacheMock.verify(Fruit.class).persist();
PanacheMock.verifyNoMoreInteractions(Fruit.class);
}
}
When running ./mvnw verify
on the attached reproducer I’m getting
2021-04-01 09:04:05,510 ERROR [org.jbo.res.rea.com.cor.AbstractResteasyReactiveContext] (executor-thread-1) Request failed: javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: org.acme.domain.Fruit
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:726)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:706)
at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.persist(TransactionScopedSession.java:139)
at io.quarkus.hibernate.orm.runtime.session.ForwardingSession.persist(ForwardingSession.java:53)
at io.quarkus.hibernate.orm.panache.common.runtime.AbstractJpaOperations.persist(AbstractJpaOperations.java:99)
at io.quarkus.hibernate.orm.panache.common.runtime.AbstractJpaOperations.persist(AbstractJpaOperations.java:94)
at io.quarkus.hibernate.orm.panache.PanacheEntityBase.persist(PanacheEntityBase.java:56)
at org.acme.rest.FruitResource.addFruit(FruitResource.java:23)
at org.acme.rest.FruitResource_Subclass.addFruit$$superaccessor1(FruitResource_Subclass.zig:265)
at org.acme.rest.FruitResource_Subclass$$function$$1.apply(FruitResource_Subclass$$function$$1.zig:33)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
at io.quarkus.hibernate.validator.runtime.interceptor.AbstractMethodValidationInterceptor.validateMethodInvocation(AbstractMethodValidationInterceptor.java:68)
at io.quarkus.hibernate.validator.runtime.jaxrs.QuarkusRestEndPointValidationInterceptor.validateMethodInvocation(QuarkusRestEndPointValidationInterceptor.java:19)
at io.quarkus.hibernate.validator.runtime.jaxrs.QuarkusRestEndPointValidationInterceptor_Bean.intercept(QuarkusRestEndPointValidationInterceptor_Bean.zig:341)
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:50)
at io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorBase.invokeInOurTx(TransactionalInterceptorBase.java:127)
at io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorBase.invokeInOurTx(TransactionalInterceptorBase.java:100)
at io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorRequired.doIntercept(TransactionalInterceptorRequired.java:32)
at io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorBase.intercept(TransactionalInterceptorBase.java:53)
at io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorRequired.intercept(TransactionalInterceptorRequired.java:26)
at io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorRequired_Bean.intercept(TransactionalInterceptorRequired_Bean.zig:340)
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
at org.acme.rest.FruitResource_Subclass.addFruit(FruitResource_Subclass.zig:222)
at org.acme.rest.FruitResource$quarkusrestinvoker$addFruit_e4ceee061fa3e84c0954c49287079e5dcbfcc5b0.invoke(FruitResource$quarkusrestinvoker$addFruit_e4ceee061fa3e84c0954c49287079e5dcbfcc5b0.zig:39)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:7)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:122)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at java.base/java.lang.Thread.run(Thread.java:834)
at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: org.acme.domain.Fruit
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:120)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:93)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:720)
... 35 more
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 13.704 s <<< FAILURE! - in org.acme.rest.FruitResourceTests
I also tried PanacheMock.doNothing().when(Fruit.class).persist(Mockito.any(Fruit.class));
while changing the JAX-RS method to do Fruit.persist(fruit);
instead of fruit.persist();
, but that resulted in a much different error:
[ERROR] org.acme.rest.FruitResourceTests.addFruit Time elapsed: 0.28 s <<< ERROR!
java.lang.NullPointerException
at org.acme.rest.FruitResourceTests.addFruit(FruitResourceTests.java:83)
[ERROR] org.acme.rest.FruitResourceTests.getAll Time elapsed: 0.009 s <<< ERROR!
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at io.quarkus.panache.mock.PanacheMock.doNothing(PanacheMock.java:120)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
at org.acme.rest.FruitResourceTests.getAll(FruitResourceTests.java:23)
2021-03-30 16:15:57,464 INFO [io.quarkus] (main) Quarkus stopped in 0.631s
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR] FruitResourceTests.addFruit:83 » NullPointer
[ERROR] FruitResourceTests.getAll:23 » UnfinishedStubbing
Unfinished stubbing detecte...
To Reproduce
Reproducer project attached
Steps to reproduce the behavior:
- Run
./mvnw verify
Configuration
# Add your application.properties here, if applicable.
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=fruits
quarkus.datasource.password=fruits
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/fruits
quarkus.datasource.devservices.image-name=quay.io/edeandrea/postgres-13-fruits:latest
quarkus.hibernate-orm.database.generation=validate
# Set testcontainers info for testing
%test.quarkus.datasource.jdbc.url=jdbc:tc:postgresql:13:///test?TC_INITSCRIPT=db/schema.sql
%test.quarkus.datasource.jdbc.driver=org.testcontainers.jdbc.ContainerDatabaseDriver
Environment (please complete the following information):
Output of uname -a
or ver
Darwin edeandre-mac 20.3.0 Darwin Kernel Version 20.3.0: Thu Jan 21 00:07:06 PST 2021; root:xnu-7195.81.3~1/RELEASE_X86_64 x86_64
Output of java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
GraalVM version (if different from Java)
Quarkus version or git rev
1.13.0.Final
(I also tried with 1.12.x and same thing)
Build tool (ie. output of mvnw --version
or gradlew --version
)
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /Users/edeandre/.m2/wrapper/dists/apache-maven-3.6.3-bin/1iopthnavndlasol9gbrbg6bf2/apache-maven-3.6.3
Java version: 11.0.2, vendor: Oracle Corporation, runtime: /Users/edeandre/.sdkman/candidates/java/11.0.2-open
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "10.16", arch: "x86_64", family: "mac"
Issue Analytics
- State:
- Created 2 years ago
- Comments:22 (21 by maintainers)
I have a PR that fixes it for the static methods. As for
entity.persist()
and other instance methods, my opinion is that you can mock them by mocking theEntityManager
which we ultimately delegate to, but I hit a bug at https://github.com/quarkusio/quarkus/pull/15092#issuecomment-814767337 which we probably have to fix.What’s the status on getting this into a 1.13.x release? It is very important for me.