Repository navigation
Paths and Nodes
A violation's getPropertyPath() is a jakarta.validation.Path: a sequence of Path.Nodes, each of an
ElementKind. assertThatPath(Path) returns an AbstractPathAssert<?>; it is also reached from a violation with
extractingPropertyPath().
AbstractPathAssert extends AbstractAssert<SELF, Path> and implements AssertJ's EnumerableAssert. It is
deliberately not an AbstractIterableAssert: that would require filteredOn to return a Path, and a filtered
subset of nodes is not a path. So the path assertion has the size vocabulary, and nodes() has the rest.
| On the path | |
|---|---|
| size |
isEmpty(), isNotEmpty(), isNullOrEmpty(), hasSize(int), hasSizeGreaterThan(int), hasSizeGreaterThanOrEqualTo(int), hasSizeLessThan(int), hasSizeLessThanOrEqualTo(int), hasSizeBetween(int, int), hasSameSizeAs(Iterable), hasSameSizeAs(Object array)
|
| nodes as a list |
nodes() — a ListAssert<Path.Node>: filteredOn, contains, allSatisfy, extracting, … |
| node by position |
extractingNode(int) and the typed extracting…Node(int) below |
| not supported |
usingElementComparator(…), usingDefaultElementComparator() throw UnsupportedOperationException; configure a comparator on nodes() instead |
assertThatPath(path).hasSize(2);
assertThatPath(path).nodes().filteredOn(Path.Node::isInIterable).isEmpty();For a node of a known kind at a known index, use the typed navigation; it returns the node assertion for that kind.
Kind (ElementKind) |
Node type | Extract | Consumer form | Standalone entry point |
|---|---|---|---|---|
| any | Path.Node |
extractingNode(int) |
hasNodeSatisfying(int, Consumer) |
assertThatNode |
BEAN |
Path.BeanNode |
extractingBeanNode(int) |
— | assertThatBeanNode |
PROPERTY |
Path.PropertyNode |
extractingPropertyNode(int) |
hasPropertyNodeSatisfying(int, Consumer) |
assertThatPropertyNode |
METHOD |
Path.MethodNode |
extractingMethodNode(int) |
hasMethodNodeSatisfying(int, Consumer) |
assertThatMethodNode |
CONSTRUCTOR |
Path.ConstructorNode |
extractingConstructorNode(int) |
hasConstructorNodeSatisfying(int, Consumer) |
assertThatConstructorNode |
PARAMETER |
Path.ParameterNode |
extractingParameterNode(int) |
hasParameterNodeSatisfying(int, Consumer) |
assertThatParameterNode |
CROSS_PARAMETER |
Path.CrossParameterNode |
extractingCrossParameterNode(int) |
hasCrossParameterNodeSatisfying(int, Consumer) |
assertThatCrossParameterNode |
RETURN_VALUE |
Path.ReturnValueNode |
extractingReturnValueNode(int) |
hasReturnValueNodeSatisfying(int, Consumer) |
assertThatReturnValueNode |
CONTAINER_ELEMENT |
Path.ContainerElementNode |
extractingContainerElementNode(int) |
hasContainerElementNodeSatisfying(int, Consumer) |
assertThatContainerElementNode |
Each extracting…Node(int) also has an overload taking an AssertFactory, and the general form is
extractingNode(int index, Class<N> nodeType, AssertFactory<? super N, ? extends A> factory) with
hasNodeSatisfying(int, Class<N>, AssertFactory, Consumer).
The path of a method-parameter violation has two nodes — the method, then the parameter:
final Method greet = Greeter.class.getMethod("greet", String.class, int.class);
final Path path = validator.forExecutables()
.validateParameters(new Greeter("Jane"), greet, new Object[]{" ", 1})
.iterator().next()
.getPropertyPath(); // greet.arg0
assertThatPath(path)
.hasSize(2)
.hasMethodNodeSatisfying(0, n -> n.hasName("greet"))
.hasNodeSatisfying(1, n -> n.hasKind(ElementKind.PARAMETER));
assertThatPath(path)
.extractingMethodNode(0)
.hasParameterTypes(String.class, int.class);
assertThatPath(path)
.extractingParameterNode(1)
.hasParameterIndex(0);Every node assertion has the properties common to Path.Node:
| Property | Extract | Check |
|---|---|---|
getName() |
extractingName() |
hasName(String), doesNotHaveName(), hasNameSatisfying(Consumer)
|
getKind() |
extractingKind() |
hasKind(ElementKind), hasKindSatisfying(Consumer)
|
getIndex() |
extractingIndex() |
hasIndex(Integer), doesNotHaveIndex(), hasIndexSatisfying(Consumer)
|
getKey() |
extractingKey() |
hasKey(Object), doesNotHaveKey(), hasKeySatisfying(Consumer)
|
isInIterable() |
extractingInIterable() |
isInIterable(), isNotInIterable(), hasInIterableSatisfying(Consumer)
|
and, by kind, the properties of its node type, through the mixin interfaces nested in AbstractPathAssert:
| Mixin | Methods | Implemented by |
|---|---|---|
HasContainerClass |
extractingContainerClass(), hasContainerClass(Class), doesNotHaveContainerClass(), hasContainerClassSatisfying(…)
|
bean, property, container-element nodes |
HasTypeArgumentIndex |
extractingTypeArgumentIndex(), hasTypeArgumentIndex(Integer), doesNotHaveTypeArgumentIndex(), hasTypeArgumentIndexSatisfying(…)
|
bean, property, container-element nodes |
HasParameterTypes |
extractingParameterTypes(), hasParameterTypes(Class...), hasParameterTypesSatisfying(…)
|
method, constructor nodes |
HasParameterIndex |
extractingParameterIndex(), hasParameterIndex(Integer), doesNotHaveParameterIndex(), hasParameterIndexSatisfying(…)
|
parameter nodes |
Cross-parameter and return-value nodes have the common properties only. Each extracting… and …Satisfying also
has an overload taking an AssertFactory.
A list element is a property node followed by a container-element node. For the fixture Bagged, whose
List<@NotBlank String> tags holds a blank string:
assertThatPath(tagsPath).extractingPropertyNode(0)
.hasName("tags")
.doesNotHaveContainerClass()
.doesNotHaveTypeArgumentIndex()
.isNotInIterable();
assertThatPath(tagsPath).extractingContainerElementNode(1)
.hasKind(ElementKind.CONTAINER_ELEMENT)
.hasContainerClass(List.class)
.hasTypeArgumentIndex(0)
.hasIndex(0)
.isInIterable();A class-level constraint yields a single BEAN node, the only kind whose name is null:
assertThatPath(beanPath).extractingBeanNode(0)
.hasKind(ElementKind.BEAN)
.doesNotHaveName()
.doesNotHaveIndex()
.doesNotHaveKey()
.isNotInIterable();Narrowing a node to a subtype is checked against Path.Node.getKind(), never with instanceof.
Hibernate Validator's node implementation implements every Path.Node subtype at once, so
Path.PropertyNode.class.isInstance(node) is true even for a METHOD node; PathAssert_NodeKind_Test asserts that
premise. And Path.Node.as(Class) is specified to throw a ClassCastException for the wrong kind, with the advice to
check getKind() first.
So extractingPropertyNode(0) on a method node fails as an AssertionError naming both kinds, rather than succeeding
by instanceof and failing later inside the provider:
Expecting the node at index 0
<…the node's toString()…>
to be of kind
<PROPERTY>
so that it can be narrowed to <PropertyNode>, but its kind is
<METHOD>
The node factories in InstanceOf Factories (PROPERTY_NODE, METHOD_NODE, …) apply the same check, since the
instanceof test that asInstanceOf performs first cannot discriminate. The standalone assertThat…Node entry
points take an already-typed node and do not check; narrow with node.as(…) only after checking the kind.
For every positional navigation — extractingNode, the typed extracting…Node, the has…NodeSatisfying forms, and
extractingParameterDescriptor(int) on Metadata:
| Index | Result |
|---|---|
| negative |
IllegalArgumentException — a programming error in the test |
| at or beyond the number of nodes (or parameters) |
AssertionError: Expecting a node at index 2 of <greet.arg0> but it has 2 node(s)
|
| a node of another kind, for a typed navigation |
AssertionError, as above |
Both failures are raised through the path assertion, so what was configured on it is kept:
assertThatPath(path).as("case 42").extractingNode(2) fails with [case 42] in the message, and a
withFailMessage(…) replaces the text. The navigated node assertion — and the list from nodes() — inherits the
description and the overriding message too.
In 0.6.1 a missing index surfaced as NoSuchElementException (paths) or IndexOutOfBoundsException
(parameter descriptors).
assertj-validation · Apache License 2.0 · io.github.jinahya:assertj-validation · Jakarta Validation 3.0 / 3.1 · AssertJ 3.27+ · Java 17+
Running validation
Inspecting results
Reference
External