Write a test that asserts a registrar registered reflection access to a type's declared constructors.
answer
- new RuntimeHints() → registerHints → assertThat.accepts
- reflection().onType(X).withMemberCategory(...)
- MemberCategory.INVOKE_DECLARED_CONSTRUCTORS
- subset match: contains-at-least
- rejects() for over-registration
basics
~10 sRun the registrar against a fresh RuntimeHints, then assert RuntimeHintsPredicates.reflection().onType(MyType.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS) accepts those hints using AssertJ's .accepts(hints).
solid answer
~30 sYou create a `RuntimeHints` instance, invoke your `RuntimeHintsRegistrar.registerHints(hints, classLoader)`, then assert with a reflection predicate refined by member category. `RuntimeHintsPredicates.reflection().onType(X.class)` checks a reflection hint exists on the type; chaining `.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)` narrows it to require that the type's declared constructors are invokable. Wrap it in AssertJ: `assertThat(predicate).accepts(hints)` (or `.rejects(hints)` for the negative case). The member-category enum values (e.g. `INVOKE_DECLARED_CONSTRUCTORS`, `INVOKE_PUBLIC_METHODS`, `DECLARED_FIELDS`) mirror what you'd have passed to `hints.reflection().registerType(...)`. The predicate matches as long as the requested category is present in the registered hint — extra categories on the hint don't cause a rejection.
code
java · 27 linesimport org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import static org.assertj.core.api.Assertions.assertThat;
class MyHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(MyDto.class,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
class MyHintsTests {
@Test
void contributesConstructorHint() {
RuntimeHints hints = new RuntimeHints();
new MyHints().registerHints(hints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.reflection()
.onType(MyDto.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
.accepts(hints);
}
}go deeper
Recognize the three-step shape and that member categories exist.
Can write the full test and pick the correct MemberCategory; knows accepts vs rejects.
Understands contains-at-least matching and the over-broad onType pitfall; uses rejects for negative assertions.
Would codify this as a convention for library hint tests and review member-category precision.
## The standard test shape Every hint predicate test follows the same three steps: **(1)** build an empty `RuntimeHints`, **(2)** drive your contribution into it, **(3)** assert with a predicate. ```java import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import static org.assertj.core.api.Assertions.assertThat; class MyHintsTests { @Test void registersConstructorReflectionForDto() { RuntimeHints hints = new RuntimeHints(); new MyHints().registerHints(hints, getClass().getClassLoader()); assertThat(RuntimeHintsPredicates.reflection() .onType(MyDto.class) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) .accepts(hints); } } ``` ## Anatomy of the reflection predicate - `RuntimeHintsPredicates.reflection()` returns a `ReflectionHintsPredicates` builder. - `.onType(Class)` (or `.onType(TypeReference)`) → a predicate that's true when *any* reflection hint exists for that type. - `.onConstructor(Constructor)`, `.onMethod(Class, name)`, `.onField(Class, name)` → narrow to a specific member. - `.withMemberCategory(MemberCategory)` → require a single category is present on the type hint. - `.withMemberCategories(MemberCategory...)` → require **all** listed categories. - `.withAnyMemberCategory(MemberCategory...)` → require **at least one** of the listed categories. Common `MemberCategory` values: `INVOKE_DECLARED_CONSTRUCTORS`, `INVOKE_PUBLIC_CONSTRUCTORS`, `INVOKE_DECLARED_METHODS`, `INVOKE_PUBLIC_METHODS`, `DECLARED_FIELDS`, `PUBLIC_FIELDS`. ## Matching semantics — the key gotcha The predicate is a **'contains at least'** check. `.withMemberCategory(INVOKE_DECLARED_CONSTRUCTORS)` accepts a hint that was registered with `INVOKE_DECLARED_CONSTRUCTORS` *and* `INVOKE_DECLARED_METHODS`. So a predicate that asserts a subset will pass even if the registrar was more generous. Conversely, `.onType(X)` **without** a member category accepts as soon as the type is registered *at all* (even just for introspection) — so an over-broad assertion can hide the fact that a needed category is missing. Assert the specific category you actually depend on. ## accepts vs rejects `assertThat(predicate).accepts(hints)` asserts the predicate returns `true`; `.rejects(hints)` asserts `false`. Use `.rejects` to prove you did **not** over-register (e.g. that an internal type was *not* exposed to reflection). ## Kotlin note In Kotlin the same API applies; use `MyDto::class.java` for the `Class` argument. The AssertJ assertion and predicate chaining are identical.
- Difference between withMemberCategory, withMemberCategories, and withAnyMemberCategory?withMemberCategory requires one specific category present; withMemberCategories requires ALL listed categories present; withAnyMemberCategory requires AT LEAST ONE of the listed categories.
- Why might onType(X) with no member category be a weak assertion?It passes as soon as the type is registered for any reason, including bare introspection, so it won't catch a missing INVOKE category your code actually needs. Assert the specific category.