javaquarkusvert.xreactivequarkus-panache

Quarkus Reactive Test with Panache problem (chaining two DB calls)


Preamble

I am currently switching from Spring to Quarkus using the book "Full Stack Quarkus and React" by Marc Nuri San Felix. Since the book uses an older version of Quarkus (and dependent libs) I am trying to transfer the examples to something, that works with the current version.

Problem

In chapter 5 (Testing your backend) there is a Quarkus Test example like this:

@Test
  @TestSecurity(user = "user", roles = "user")
  void updateForbidden() {
    final User admin = User.<User>findById(0L).await().indefinitely();
    Task adminTask = new Task();
    adminTask.title = "admins-task";
    adminTask.user = admin;
    adminTask = adminTask.<Task>persistAndFlush().await().indefinitely();
    given()
      .body("{\"title\":\"to-update\"}")
      .contentType(ContentType.JSON)
      .when().put("/api/v1/tasks/" + adminTask.id)
      .then()
      .statusCode(401); // TODO: TaskService UnauthorizedException should be changed to ForbiddenException
  }

If I run this example with Quarkus 3.6.3, I get the following error message:

java.lang.IllegalStateException: No current Vertx context found

    at io.quarkus.hibernate.reactive.panache.common.runtime.SessionOperations.vertxContext(SessionOperations.java:191)
    at io.quarkus.hibernate.reactive.panache.common.runtime.SessionOperations.getSession(SessionOperations.java:141)
    at io.quarkus.hibernate.reactive.panache.common.runtime.AbstractJpaOperations.getSession(AbstractJpaOperations.java:364)
    at io.quarkus.hibernate.reactive.panache.common.runtime.AbstractJpaOperations.findById(AbstractJpaOperations.java:98)

I looked all around Stack Overflow and found hints, that I have to use the Annotations either @RunOnVertxContext or @TestReactiveTransaction, but neither worked like I suspected. I spend several hours trying different code snippets. My current one is

    @Test
    @TestSecurity(user = "user", roles = "user")
    @TestReactiveTransaction
    void updateForbidden(UniAsserter asserter) {
        Task adminTask = new Task();
        asserter.execute(() -> {
            User.findById(0L).subscribe().with(admin -> {
                System.out.println("User found: " + ((User) admin).name);
                adminTask.title = "admins-task";
                adminTask.user = (User) admin;
                adminTask.<Task>persistAndFlush().subscribe().with(updatedAdminTask -> {
                    System.out.println("UpdatedAdminTask: " + updatedAdminTask);
                }, throwable -> {
                    System.err.println("There is something wrong ...");
                    throwable.printStackTrace();
                });
            });
        });
    }

which results in the following log:

2023-12-21 17:09:37,834 DEBUG [org.hib.sql.res.int.ResultsHelper] (vert.x-eventloop-thread-1) Collection fully initialized: [test.user.User.roles#0]
User found: admin
2023-12-21 17:09:44,448 DEBUG [org.hib.SQL] (vert.x-eventloop-thread-1) select nextval('tasks_id_seq')
2023-12-21 17:10:06,185 FINE  [org.jun.jup.eng.exe.InvocationInterceptorChain$ValidatingInvocation] (main) The invocation is skipped
2023-12-21 17:10:06,190 DEBUG [org.hib.rea.eve.imp.AbstractReactiveSaveEventListener] (vert.x-eventloop-thread-1) Generated identifier: 4, using strategy: org.hibernate.reactive.id.impl.ReactiveGeneratorWrapper
2023-12-21 17:10:06,199 DEBUG [io.qua.run.Application] (main) Stopping application
2023-12-21 17:10:06,199 DEBUG [io.qua.run.shu.ShutdownRecorder] (main) Attempting to gracefully shutdown.
2023-12-21 17:10:06,215 DEBUG [org.hib.eng.tra.int.TransactionImpl] (vert.x-eventloop-thread-1) TransactionImpl created on closed Session/EntityManager
2023-12-21 17:10:06,215 DEBUG [org.hib.eng.tra.int.TransactionImpl] (vert.x-eventloop-thread-1) On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
There is something wrong ...
org.hibernate.HibernateException: java.util.concurrent.CompletionException: java.lang.IllegalStateException: Session/EntityManager is closed
    at org.hibernate.reactive.session.impl.ReactiveExceptionConverter.convert(ReactiveExceptionConverter.java:28)
    at org.hibernate.reactive.session.impl.ReactiveSessionImpl.lambda$firePersist$11(ReactiveSessionImpl.java:741)
    at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
    at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147)
    at io.vertx.core.Future.lambda$toCompletionStage$3(Future.java:536)
    at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:141)
    at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:60)
    at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:211)
    at io.vertx.core.impl.future.PromiseImpl.tryComplete(PromiseImpl.java:23)
    at io.vertx.sqlclient.impl.QueryResultBuilder.tryComplete(QueryResultBuilder.java:88)
    at io.vertx.sqlclient.impl.QueryResultBuilder.tryComplete(QueryResultBuilder.java:32)
    at io.vertx.core.Promise.complete(Promise.java:66)
    at io.vertx.core.Promise.handle(Promise.java:51)
    at io.vertx.core.Promise.handle(Promise.java:29)
    at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:141)
    at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:54)
    at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:86)
    at io.vertx.core.impl.DuplicatedContext.execute(DuplicatedContext.java:184)
    at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:51)
    at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:211)
    at io.vertx.core.impl.future.PromiseImpl.tryComplete(PromiseImpl.java:23)
    at io.vertx.core.impl.future.PromiseImpl.onSuccess(PromiseImpl.java:49)
    at io.vertx.core.impl.future.PromiseImpl.handle(PromiseImpl.java:41)
    at io.vertx.core.impl.future.PromiseImpl.handle(PromiseImpl.java:23)
    at io.vertx.sqlclient.impl.command.CommandResponse.fire(CommandResponse.java:46)
    at io.vertx.sqlclient.impl.SocketConnectionBase.handleMessage(SocketConnectionBase.java:325)
    at io.vertx.pgclient.impl.PgSocketConnection.handleMessage(PgSocketConnection.java:118)
    at io.vertx.sqlclient.impl.SocketConnectionBase.lambda$init$0(SocketConnectionBase.java:138)
    at io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:55)
    at io.vertx.core.impl.ContextBase.emit(ContextBase.java:297)
    at io.vertx.core.net.impl.NetSocketImpl.handleMessage(NetSocketImpl.java:378)
    at io.vertx.core.net.impl.ConnectionBase.read(ConnectionBase.java:159)
    at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:153)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)

Does anybody have any ideas how to make this (chain two Panache calls) work?


Solution

  • I'm Marc the book author.

    You can find an updated version of the code on my personal repository in the spike-v3 directory of the feat/quarkus-3 branch: https://github.com/manusa/packt-fullstack-quarkus-react/tree/946a4400dfb5ceda3d286a7d6ebe402ec72875cd/spike-v3

    The updated test, which should be compliant with Quarkus 3 is:

      @Test
      @TestSecurity(user = "user", roles = "user")
      @RunOnVertxContext
      void updateForbidden(UniAsserter uniAsserter) {
        uniAsserter.assertThat(() ->
            Panache.withSession(() -> User.<User>findById(0L).chain(admin -> {
                Task task = new Task();
                task.title = "admins-task";
                task.user = admin;
                return task.<Task>persistAndFlush();
              })
            ),
          adminTask -> {
            given()
              .body("{\"title\":\"to-update\"}")
              .contentType(ContentType.JSON)
              .when().put("/api/v1/tasks/" + adminTask.id)
              .then()
              .statusCode(401);
          }
        );
      }
    

    Note the usage of the @RunOnVertxContext annotation (as you suggested), but specially the usage of the UniAsserter class which allows the assertions to occur on the Vert.x event loop thread.

    Hopefully, the Packt folks will let me publish a second edition updated for the latest Quarkus and ReactJS releases. Until then, you can check the repository I linked.