question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Mocking Panache active record pattern not working

See original GitHub issue

Describe 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:

  1. 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"

code-with-quarkus.zip

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:22 (21 by maintainers)

github_iconTop GitHub Comments

1reaction
FroMagecommented, Apr 7, 2021

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 the EntityManager 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.

0reactions
edeandreacommented, Apr 23, 2021

What’s the status on getting this into a 1.13.x release? It is very important for me.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Simplified Hibernate ORM with Panache - Quarkus
Using the active record pattern. If you are using the active record pattern you cannot use Mockito directly as it does not support...
Read more >
Quarkus Panache active pattern unit test mocking
I'm trying to unit test service bean and mock persistence unit inside of the service. For persistence unit I'm using active record pattern...
Read more >
Panache - Active Record Pattern : r/java - Reddit
No mocking required. I honestly don't understand why this isn't more common. I always write the majority of code this way - immutable...
Read more >
Panache - Active Record Pattern - Thorben Janssen
The main idea of the active record pattern is to let the entity object encapsulate the data and the database operations you can...
Read more >
REST API using Quarkus and Panache | by Manollo Guedes
If you have any problem you can check on the final code we have developed together so far! Final Bonus: Active Record Pattern....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found