quarkusio/quarkus · error · IllegalStateException

A test method annotated with @TestReactiveTransaction must…

Error message

A test method annotated with @TestReactiveTransaction must accept io.quarkus.test.vertx.UniAsserter

What it means

Methods annotated with @TestReactiveTransaction run under a reactive transaction and must use the UniAsserter callback style of Quarkus reactive testing; the interceptor detects whether the method has a UniAsserter parameter. If it does not, there is no way to drive the reactive chain, so the interceptor throws immediately at call time.

Solutions

  1. Add an io.quarkus.test.vertx.UniAsserter parameter to the test method and use asserter.assertThat/Uni assertions
  2. Remove @TestReactiveTransaction if the test does not need a reactive transaction
  3. Use plain @TestReactiveTransaction with UniAsserter, or switch to inject + manually chaining Uni for non-test code

Example fix

// before
@TestReactiveTransaction
void testSave() {
    entity.persist();
}
// after
@TestReactiveTransaction
void testSave(UniAsserter asserter) {
    asserter.assertThat(() -> entity.persist().chain(() -> entity.count()), count -> count == 1);
}
Defensive patterns

Strategy: validation

Validate before calling

if (!java.util.Arrays.stream(testMethod.getParameterTypes()).anyMatch(p -> p == io.quarkus.test.vertx.UniAsserter.class)) {
    throw new AssertionError("@TestReactiveTransaction test must accept UniAsserter");
}

Prevention

When it happens

Trigger: A test method annotated with @TestReactiveTransaction is invoked by the interceptor and its Method has no io.quarkus.test.vertx.UniAsserter parameter (isSpecialTestMethod returns false).

Common situations: Writing a plain void/synchronous test method and annotating it with @TestReactiveTransaction expecting declarative transaction semantics; copy-pasting @Transactional test style into a reactive test; renaming/refactoring that removed the UniAsserter parameter.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/ec6ae739d6a60f12. Report an issue: GitHub.

Appendix: source

Thrown at extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/TestReactiveTransactionalInterceptor.java:26

import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.InvocationContext;

import io.smallrye.mutiny.Uni;

public class TestReactiveTransactionalInterceptor {

    private static final String JUNIT_TEST_ANN = "org.junit.jupiter.api.Test";
    private static final String JUNIT_BEFORE_EACH_ANN = "org.junit.jupiter.api.BeforeEach";
    private static final String JUNIT_AFTER_EACH_ANN = "org.junit.jupiter.api.AfterEach";
    private static final String UNI_ASSERTER_CLASS = "io.quarkus.test.vertx.UniAsserter";

    @AroundInvoke
    public Object intercept(InvocationContext context) throws Exception {
        if (isSpecialTestMethod(context)) {
            return handleSpecialTestMethod(context);
        }
        // TODO validate this requirement during build
        throw new IllegalStateException(
                "A test method annotated with @TestReactiveTransaction must accept io.quarkus.test.vertx.UniAsserter");
    }

    protected boolean isSpecialTestMethod(InvocationContext ic) {
        Method method = ic.getMethod();
        return hasParameter(UNI_ASSERTER_CLASS, method)
                && (hasAnnotation(JUNIT_TEST_ANN, method)
                        || hasAnnotation(JUNIT_BEFORE_EACH_ANN, method)
                        || hasAnnotation(JUNIT_AFTER_EACH_ANN, method));
    }

    protected Object handleSpecialTestMethod(InvocationContext ic) {
        // let's not deal with generics/erasure
        Class<?>[] parameterTypes = ic.getMethod().getParameterTypes();
        Object uniAsserter = null;
        Class<?> uniAsserterClass = null;
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> klass = parameterTypes[i];

View on GitHub (pinned to e1c734241f)