Skip to content

Commit

Permalink
Merge pull request #34334 from mkouba/test-hibernate-reactive-panache
Browse files Browse the repository at this point in the history
Introduce quarkus-test-hibernate-reactive-panache module
  • Loading branch information
mkouba authored Jun 29, 2023
2 parents 8632e3e + 67d8c23 commit 283085f
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 28 deletions.
5 changes: 5 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2996,6 +2996,11 @@
<artifactId>quarkus-test-vertx</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-hibernate-reactive-panache</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arquillian</artifactId>
Expand Down
37 changes: 13 additions & 24 deletions docs/src/main/asciidoc/hibernate-reactive-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -855,45 +855,34 @@ public class PersonRepository implements PanacheRepositoryBase<Person,Integer> {

Testing reactive Panache entities in a `@QuarkusTest` is slightly more complicated than testing regular Panache entities due to the asynchronous nature of the APIs and the fact that all operations need to run on a Vert.x event loop.

The `quarkus-test-vertx` dependency provides the `@io.quarkus.test.vertx.RunOnVertxContext` annotation and the `io.quarkus.test.vertx.UniAsserter` class which are intended precisely for this purpose.
The `quarkus-test-vertx` dependency provides the `@io.quarkus.test.vertx.RunOnVertxContext` annotation and the `io.quarkus.test.vertx.UniAsserter` class which are intended precisely for this purpose.
The usage is described in the xref:hibernate-reactive.adoc#testing[Hibernate Reactive] guide.

You can also extend the `io.quarkus.test.vertx.UniAsserterInterceptor` to wrap the injected `UniAsserter` and customize the behavior.
For example, the interceptor can be used to execute the assert methods within a separate database transaction.
Moreover, the `quarkus-test-hibernate-reactive-panache` dependency provides the `io.quarkus.test.hibernate.reactive.panache.TransactionalUniAsserter` that can be injected as a method parameter of a test method annotated with `@RunOnVertxContext`.
The `TransactionalUniAsserter` is a `io.quarkus.test.vertx.UniAsserterInterceptor` that wraps each assert method within a separate reactive transaction.

.`UniAsserterInterceptor` Example
.`TransactionalUniAsserter` Example
[source,java]
----
import io.quarkus.test.vertx.UniAsserterInterceptor;
import io.quarkus.test.hibernate.reactive.panache.TransactionalUniAsserter;
@QuarkusTest
public class SomeTest {
static class TransactionalUniAsserterInterceptor extends UniAsserterInterceptor {
public TransactionUniAsserterInterceptor(UniAsserter asserter) {
super(asserter);
}
@Override
protected <T> Supplier<Uni<T>> transformUni(Supplier<Uni<T>> uniSupplier) {
// Assert/execute methods are invoked within a database transaction
return () -> Panache.withTransaction(uniSupplier);
}
}
@Test
@RunOnVertxContext
public void testEntity(UniAsserter asserter) {
asserter = new TransactionalUniAsserterInterceptor(asserter); <1>
asserter.execute(() -> new MyEntity().persist());
asserter.assertEquals(() -> MyEntity.count(), 1l);
asserter.execute(() -> MyEntity.deleteAll());
public void testEntity(TransactionalUniAsserter asserter) {
asserter.execute(() -> new MyEntity().persist()); <1>
asserter.assertEquals(() -> MyEntity.count(), 1l); <2>
asserter.execute(() -> MyEntity.deleteAll()); <3>
}
}
----
<1> The `TransactionalUniAsserterInterceptor` wraps the injected `UniAsserter`.
<1> The first reactive transaction is used to persist the entity.
<2> The second reactive transaction is used to count the entities.
<3> The third reactive transaction is used to delete all entities.

Of course, you can also define a custom `UniAsserterInterceptor` to wrap the injected `UniAsserter` and customize the behavior.

== Mocking

Expand Down
2 changes: 1 addition & 1 deletion integration-tests/hibernate-reactive-panache/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-vertx</artifactId>
<artifactId>quarkus-test-hibernate-reactive-panache</artifactId>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.it.panache.reactive;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import org.junit.jupiter.api.Test;

import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.test.hibernate.reactive.panache.TransactionalUniAsserter;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.vertx.RunOnVertxContext;

@QuarkusTest
public class TransactionalUniAsserterTest {

@RunOnVertxContext
@Test
public void testTransactionalUniAsserter(TransactionalUniAsserter asserter) {
assertNotNull(asserter);
asserter.assertThat(() -> Panache.currentTransaction(), transaction -> {
assertNotNull(transaction);
assertFalse(transaction.isMarkedForRollback());
asserter.putData("tx", transaction);
});
asserter.assertThat(() -> Panache.currentTransaction(), transaction -> {
assertNotNull(transaction);
assertFalse(transaction.isMarkedForRollback());
assertNotEquals(transaction, asserter.getData("tx"));
});
}

}
35 changes: 35 additions & 0 deletions test-framework/hibernate-reactive-panache/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-framework</artifactId>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>quarkus-test-hibernate-reactive-panache</artifactId>
<name>Quarkus - Test framework - Hibernate Reactive Panache</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache-common</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-vertx</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.test.hibernate.reactive.panache;

import java.util.function.Supplier;

import io.quarkus.hibernate.reactive.panache.common.runtime.SessionOperations;
import io.quarkus.test.vertx.UniAsserter;
import io.quarkus.test.vertx.UniAsserterInterceptor;
import io.smallrye.mutiny.Uni;

/**
* A {@link UniAsserterInterceptor} that wraps each assert method within a separate reactive transaction.
*
* @see UniAsserter
*/
public final class TransactionalUniAsserter extends UniAsserterInterceptor {

TransactionalUniAsserter(UniAsserter asserter) {
super(asserter);
}

@Override
protected <T> Supplier<Uni<T>> transformUni(Supplier<Uni<T>> uniSupplier) {
return () -> SessionOperations.withTransaction(() -> uniSupplier.get());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.test.hibernate.reactive.panache;

import java.lang.reflect.Method;

import io.quarkus.test.vertx.DefaultUniAsserter;
import io.quarkus.test.vertx.RunOnVertxContextTestMethodInvoker;
import io.quarkus.test.vertx.UniAsserter;

public class TransactionalUniAsserterTestMethodInvoker extends RunOnVertxContextTestMethodInvoker {

private UniAsserter uniAsserter;

@Override
public boolean handlesMethodParamType(String paramClassName) {
return TransactionalUniAsserter.class.getName().equals(paramClassName);
}

@Override
public Object methodParamInstance(String paramClassName) {
if (!handlesMethodParamType(paramClassName)) {
throw new IllegalStateException(
"TransactionalUniAsserterTestMethodInvoker does not handle '" + paramClassName + "' method param types");
}
uniAsserter = new TransactionalUniAsserter(new DefaultUniAsserter());
return uniAsserter;
}

@Override
public boolean supportsMethod(Class<?> originalTestClass, Method originalTestMethod) {
return hasSupportedAnnotation(originalTestClass, originalTestMethod)
&& hasSupportedParams(originalTestMethod);
}

private boolean hasSupportedParams(Method originalTestMethod) {
return originalTestMethod.getParameterCount() == 1
// we need to use the class name to avoid ClassLoader issues
&& originalTestMethod.getParameterTypes()[0].getName().equals(TransactionalUniAsserter.class.getName());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.test.hibernate.reactive.panache.TransactionalUniAsserterTestMethodInvoker
1 change: 1 addition & 0 deletions test-framework/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<module>junit5-mockito</module>
<module>junit5-mockito-config</module>
<module>vertx</module>
<module>hibernate-reactive-panache</module>
<module>amazon-lambda</module>
<module>arquillian</module>
<module>devmode-test-utils</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import io.smallrye.mutiny.Uni;

class DefaultUniAsserter implements UniAsserter {
public final class DefaultUniAsserter implements UniAsserter {

private final ConcurrentMap<String, Object> data;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,26 @@ public Object methodParamInstance(String paramClassName) {

@Override
public boolean supportsMethod(Class<?> originalTestClass, Method originalTestMethod) {
return hasSupportedAnnotation(originalTestClass, originalTestMethod)
&& hasSupportedParams(originalTestMethod);
}

private boolean hasSupportedParams(Method originalTestMethod) {
return originalTestMethod.getParameterCount() == 0
|| (originalTestMethod.getParameterCount() == 1
// we need to use the class name to avoid ClassLoader issues
&& originalTestMethod.getParameterTypes()[0].getName().equals(UniAsserter.class.getName()));
}

protected boolean hasSupportedAnnotation(Class<?> originalTestClass, Method originalTestMethod) {
return hasAnnotation(RunOnVertxContext.class, originalTestMethod.getAnnotations())
|| hasAnnotation(RunOnVertxContext.class, originalTestClass.getAnnotations())
|| hasAnnotation(TestReactiveTransaction.class, originalTestMethod.getAnnotations())
|| hasAnnotation(TestReactiveTransaction.class, originalTestClass.getAnnotations());
}

// we need to use the class name to avoid ClassLoader issues
private boolean hasAnnotation(Class<? extends Annotation> annotation, Annotation[] annotations) {
protected boolean hasAnnotation(Class<? extends Annotation> annotation, Annotation[] annotations) {
return hasAnnotation(annotation.getName(), annotations);
}

Expand Down Expand Up @@ -89,7 +101,7 @@ public Object invoke(Object actualTestInstance, Method actualTestMethod, List<Ob
} else {
var handler = new RunTestMethodOnVertxBlockingContextHandler(actualTestInstance, actualTestMethod,
actualTestMethodArgs, uniAsserter);
cf = ((CompletableFuture) context.executeBlocking(handler).toCompletionStage());
cf = ((CompletableFuture<Object>) context.executeBlocking(handler).toCompletionStage());
}

try {
Expand Down

0 comments on commit 283085f

Please sign in to comment.