Quarkus で Reactive Hibernate のテストをする際にトランザクションを開始してロールバックする方法があるか検証してみました。
単純にロールバックする処理を書くのでもいいのですが、フレームワーク (Quarkus) 側で用意されている仕組みを探してみました。フレームワークの仕組みに沿って書くとコードが短くなるのと、テスト中に例外が発生した場合もうまく処理してくれるだろうという考えです。
テストコード(コミットされる)
今回テストするコードは以下です。DB へデータを登録して、登録したデータを取得できるかをテストします。
@QuarkusTest
class ReactiveEntityTest {
@Inject
Session session;
@Test
void testInsert() {
var apple = new Fruit();
apple.name = "りんご";
var subscriber = apple.<Fruit>persist()
// DB へ反映
.call(e -> session.flush())
// 1 次キャッシュクリア
.invoke(e -> session.clear())
// 登録した情報を取得 (キャッシュを消したので DB から取得)
.chain(e -> Fruit.<Fruit>findById(e.id))
// 購読者を指定して処理を実施
.subscribe().withSubscriber(UniAssertSubscriber.create());
var actual = subscriber
// ブロックして結果を待つ
.awaitItem()
// 完了すること(例外が発生しないこと)
.assertCompleted()
// 結果を取得
.getItem();
// 結果の検証
assertEquals("りんご", actual.name);
// DB から再取得して新しいオブジェクト ID になっていることを確認
assertNotSame(System.identityHashCode(apple), System.identityHashCode(actual));
}
}
これはロールバックしていないので、テスト実行後にデータが残ります。
@TestTransaction
は使えない
Quarkus のガイドには @TestTransaction
アノテーションを使うよう書かれていますが、これは imperative (reactive じゃない方) の書き方なので、ロールバックされずコミットされます。
@QuarkusTest
class ReactiveEntityTest {
@Test
@TestTransaction
void testInsert() {
// テスト内容は上記と同じです
// リアクティブの場合はコミットされます
}
}
@TestReactiveTransaction
失敗例
Quarkus のソースコードを眺めていたら、 @TestReactiveTransaction
というアノテーションが使われているのを発見しました。ただこれを付ければいいというわけでは無いようで、アノテーションを付けるだけでは動作しません。
@QuarkusTest
class ReactiveEntityTest {
@Test
@TestReactiveTransaction
void testInsert() {
// テスト内容は上記と同じです
// 例外が発生します
}
}
以下のエラーが数回出た後に、
2021-11-03 09:14:22,233 WARN [io.ver.cor.imp.BlockedThreadChecker] (vertx-blocked-thread-checker) Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 8573 ms, time limit is 2000 ms: io.vertx.core.VertxException: Thread blocked
at java.base@11.0.11/jdk.internal.misc.Unsafe.park(Native Method)
at java.base@11.0.11/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:234)
at java.base@11.0.11/java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1798)
at java.base@11.0.11/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3128)
at java.base@11.0.11/java.util.concurrent.CompletableFuture.timedGet(CompletableFuture.java:1868)
at java.base@11.0.11/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2021)
at io.smallrye.mutiny.helpers.test.UniAssertSubscriber.awaitEvent(UniAssertSubscriber.java:255)
at io.smallrye.mutiny.helpers.test.UniAssertSubscriber.awaitItem(UniAssertSubscriber.java:128)
at io.smallrye.mutiny.helpers.test.UniAssertSubscriber.awaitItem(UniAssertSubscriber.java:115)
at com.zu_min.playground.quarkus.ReactiveEntityTest.testInsert(ReactiveEntityTest.java:126)
at com.zu_min.playground.quarkus.ReactiveEntityTest_Subclass.testInsert$$superforward1(ReactiveEntityTest_Subclass.zig:89)
at com.zu_min.playground.quarkus.ReactiveEntityTest_Subclass$$function$$1.apply(ReactiveEntityTest_Subclass$$function$$1.zig:24)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
at io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactionalInterceptorBase.lambda$intercept$2(ReactiveTransactionalInterceptorBase.java:49)
at io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactionalInterceptorBase$$Lambda$1411/0x0000000800ad2840.apply(Unknown Source)
at org.hibernate.reactive.mutiny.impl.MutinySessionImpl$Transaction.lambda$execute$0(MutinySessionImpl.java:434)
at org.hibernate.reactive.mutiny.impl.MutinySessionImpl$Transaction$$Lambda$1434/0x0000000800b1f440.get(Unknown Source)
at io.smallrye.mutiny.Uni.lambda$chain$0(Uni.java:494)
at io.smallrye.mutiny.Uni$$Lambda$1435/0x0000000800b1f840.apply(Unknown Source)
at io.smallrye.context.impl.wrappers.SlowContextualFunction.apply(SlowContextualFunction.java:21)
at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.performInnerSubscription(UniOnItemTransformToUni.java:68)
at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.onItem(UniOnItemTransformToUni.java:57)
at io.smallrye.mutiny.operators.uni.builders.UniCreateFromCompletionStage$CompletionStageUniSubscription.forwardResult(UniCreateFromCompletionStage.java:63)
at io.smallrye.mutiny.operators.uni.builders.UniCreateFromCompletionStage$CompletionStageUniSubscription$$Lambda$1449/0x0000000800b26840.accept(Unknown Source)
at java.base@11.0.11/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
at java.base@11.0.11/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
at java.base@11.0.11/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base@11.0.11/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
at io.vertx.core.Future.lambda$toCompletionStage$2(Future.java:360)
at io.vertx.core.Future$$Lambda$1430/0x0000000800b1e440.handle(Unknown Source)
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.core.impl.future.PromiseImpl.onSuccess(PromiseImpl.java:49)
at io.vertx.core.impl.future.PromiseImpl.handle(PromiseImpl.java:41)
at io.vertx.sqlclient.impl.TransactionImpl.lambda$wrap$0(TransactionImpl.java:72)
at io.vertx.sqlclient.impl.TransactionImpl$$Lambda$1470/0x0000000800b3f040.handle(Unknown Source)
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.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.pgclient.impl.PgSocketConnection.lambda$doSchedule$2(PgSocketConnection.java:160)
at io.vertx.pgclient.impl.PgSocketConnection$$Lambda$1474/0x0000000800b3e040.handle(Unknown Source)
at io.vertx.sqlclient.impl.command.CommandResponse.fire(CommandResponse.java:46)
at io.vertx.sqlclient.impl.SocketConnectionBase.handleMessage(SocketConnectionBase.java:287)
at io.vertx.pgclient.impl.PgSocketConnection.handleMessage(PgSocketConnection.java:96)
at io.vertx.sqlclient.impl.SocketConnectionBase.lambda$init$0(SocketConnectionBase.java:99)
at io.vertx.sqlclient.impl.SocketConnectionBase$$Lambda$1462/0x0000000800b22c40.handle(Unknown Source)
at io.vertx.core.net.impl.NetSocketImpl.lambda$new$1(NetSocketImpl.java:97)
at io.vertx.core.net.impl.NetSocketImpl$$Lambda$1456/0x0000000800b24440.handle(Unknown Source)
at io.vertx.core.streams.impl.InboundBuffer.handleEvent(InboundBuffer.java:240)
at io.vertx.core.streams.impl.InboundBuffer.write(InboundBuffer.java:130)
at io.vertx.core.net.impl.NetSocketImpl.lambda$handleMessage$9(NetSocketImpl.java:390)
at io.vertx.core.net.impl.NetSocketImpl$$Lambda$1467/0x0000000800b38c40.handle(Unknown Source)
at io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:50)
at io.vertx.core.impl.ContextImpl.emit(ContextImpl.java:274)
at io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:22)
at io.vertx.core.net.impl.NetSocketImpl.handleMessage(NetSocketImpl.java:389)
at io.vertx.core.net.impl.ConnectionBase.read(ConnectionBase.java:155)
at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:154)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
at io.vertx.pgclient.impl.codec.PgEncoder.lambda$write$0(PgEncoder.java:87)
at io.vertx.pgclient.impl.codec.PgEncoder$$Lambda$1465/0x0000000800b38440.handle(Unknown Source)
at io.vertx.pgclient.impl.codec.PgCommandCodec.handleReadyForQuery(PgCommandCodec.java:139)
at io.vertx.pgclient.impl.codec.PgDecoder.decodeReadyForQuery(PgDecoder.java:237)
at io.vertx.pgclient.impl.codec.PgDecoder.channelRead(PgDecoder.java:96)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base@11.0.11/java.lang.Thread.run(Thread.java:829)
最終的に以下の例外で失敗します。
io.smallrye.mutiny.TimeoutException
at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:58)
at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:61)
at io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactionalInterceptorBase.intercept(ReactiveTransactionalInterceptorBase.java:56)
at io.quarkus.hibernate.reactive.panache.common.runtime.TestReactiveTransactionalInterceptorGenerated_Bean.intercept(TestReactiveTransactionalInterceptorGenerated_Bean.zig:275)
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 com.zu_min.playground.quarkus.ReactiveEntityTest_Subclass.testInsert(ReactiveEntityTest_Subclass.zig:188)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:922)
at io.quarkus.test.junit.QuarkusTestExtension.interceptTestMethod(QuarkusTestExtension.java:752)
at org.junit.jupiter.engine.execution.ExecutableInvokerReflectiveInterceptorCall.lambdaReflectiveInterceptorCall.lambdaofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvokerReflectiveInterceptorCall.lambdaReflectiveInterceptorCall.lambdaofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:95)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:91)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
@TestReactiveTransaction
を使うには
まず quarkus-junit5-vertx
を maven の依存関係に追加する必要があります。pom.xml の dependencies に以下を追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-vertx</artifactId>
<scope>test</scope>
</dependency>
この状態で先ほどの @TestReactiveTransaction
を付けたテストを実行するとエラー内容が変わり、今度は以下の例外が発生します。
java.lang.RuntimeException: Unsupported return type void in method void com.zu_min.playground.quarkus.ReactiveEntityTest.testInsert(): only Uni is supported when using @ReactiveTransaction if you are running on a VertxThread
at io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactionalInterceptorBase.intercept(ReactiveTransactionalInterceptorBase.java:41)
at io.quarkus.hibernate.reactive.panache.common.runtime.TestReactiveTransactionalInterceptorGenerated_Bean.intercept(TestReactiveTransactionalInterceptorGenerated_Bean.zig:270)
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 com.zu_min.playground.quarkus.ReactiveEntityTest_Subclass.testFailureCaseOfInsertWithTestReactiveTransaction(ReactiveEntityTest_Subclass.zig:226)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at io.quarkus.test.junit.vertx.RunOnVertxContextTestMethodInvoker$RunTestMethodOnContextHandler.doRun(RunOnVertxContextTestMethodInvoker.java:129)
at io.quarkus.test.junit.vertx.RunOnVertxContextTestMethodInvoker$RunTestMethodOnContextHandler.handle(RunOnVertxContextTestMethodInvoker.java:118)
at io.quarkus.test.junit.vertx.RunOnVertxContextTestMethodInvoker$RunTestMethodOnContextHandler.handle(RunOnVertxContextTestMethodInvoker.java:89)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:63)
at io.vertx.core.impl.EventLoopContext.lambda$runOnContext$0(EventLoopContext.java:38)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:469)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:829)
void の返却はサポートしていないと言われているので、テストコードを書き替える必要があります。ただ、単純に返却を void から Uni にするだけではテストが実行されなくなる1JUnit 的には void を返すのが基本だからでしょうか ので、 UniAsserter を使って書き換える必要があるようです。
UniAsserter を使う
上記 quarkus-junit5-vertx
を追加した後に、 Uni を返すようテストコードを書き替えます。
@QuarkusTest
class ReactiveEntityTest {
@Inject
Session session;
@Test
@TestReactiveTransaction
void testInsert(UniAsserter asserter) {
var apple = new Fruit();
apple.name = "りんご";
asserter.assertThat(() -> {
return apple.<Fruit>persist()
// DB へ反映
.call(e -> session.flush())
// 1 次キャッシュクリア
.invoke(e -> session.clear())
// 登録した情報を取得 (キャッシュを消したので DB から取得)
.chain(e -> Fruit.<Fruit>findById(e.id));
}, actual -> {
// 結果の検証
assertEquals("りんご", actual.name);
// DB から再取得して新しいオブジェクト ID になっていることを確認
assertNotSame(System.identityHashCode(apple), System.identityHashCode(actual));
});
}
}
これでロールバックされるようになりました。
UniAsserter にはいろいろメソッドが用意されているので、それぞれ調べて理解すればより便利に使えそうです。
アノテーションを書く位置
今回は同じクラスに色々なパターンを書きたかったのでメソッドに @TestTransaction
や @TestReactiveTransaction
アノテーションを付けましたが、クラスにつけることも可能です。
確認した環境について
本記事の内容は Quarkus のドキュメントに書かれているわけでは無いので、今後変更される可能性があります。バージョンは 2.4.0.Final で確認しています。