Repository navigation
Validating Beans
assertThatBean(actual) returns an AbstractBeanAssert<?, ACTUAL>. Each of its methods calls the validator once,
with the groups configured at that moment, and asserts on the resulting set of violations. The assertion keeps no
result between calls, so one assertion object can be reused, reconfigured, and asserted on again.
AbstractBeanAssert extends AbstractPropertyAssert, so a bean can also be checked as a value of another bean's
property with isValidFor / isNotValidFor — see Validating Values. Method parameter and return-value validation
also hang off the bean assertion — see Executable Validation.
| Method | Jakarta Validation call | Passes when |
|---|---|---|
isValid() |
Validator.validate(actual, groups) |
the set is empty |
isValid(Consumer) |
same | the set is empty |
isNotValid() |
same | the set is not empty |
isNotValid(Consumer) |
same | the set is not empty |
hasValidProperty(String) |
Validator.validateProperty(actual, name, groups) |
the set is empty |
hasValidProperty(String, Consumer) |
same | the set is empty |
doesNotHaveValidProperty(String) |
same | the set is not empty |
Every one of them first asserts that actual is not null; a null bean fails as an AssertionError. A null
property name or consumer is a NullPointerException.
assertThatBean(new User("Jane", 28)).isValid();
assertThatBean(new User(null, 0)).isNotValid();
assertThatBean(new User("Jane", 28)).hasValidProperty("name"); // passes
assertThatBean(new User(null, 0)).hasValidProperty("age"); // passes: only "age" is validated
assertThatBean(new User("John", -1)).hasValidProperty("age"); // fails
assertThatBean(new User("John", -1)).doesNotHaveValidProperty("age"); // passeshasValidProperty and doesNotHaveValidProperty rely on Validator.validateProperty, which by specification does
not honour @Valid: the constraints declared on the property itself are checked, but the object it refers to is not
cascaded into. Use isValid() on the owning bean when the cascade matters. The same caveat applies to
Validating Values.
targetingGroups(Class<?>...) replaces the groups of the assertion. null or no argument clears them, and an
assertion with no groups validates the Default group. A null element is an IllegalArgumentException.
The groups stay configured on the assertion object until the next call:
final var assertion = assertThatBean(new User("name", 30)); // a junior: 0..59
assertion.targetingGroups(Junior.class);
assertion.isValid(); // passes: @Max(value = 59, groups = Junior.class)
assertion.targetingGroups(Senior.class);
assertion.isNotValid(); // passes: @Min(value = 60, groups = Senior.class)The groups also appear in the failure description, as groups: [interface …Junior].
targetingGroups, usingValidator and usingValidatorFactory are declared on AbstractValidationAssert and are
final; see Validator Lifecycle for the latter two.
The overloads that take a Consumer<? super Set<ConstraintViolation<ACTUAL>>> pass the set of violations to it, so a
test can inspect why:
assertThatBean(new User(null, 0)).isNotValid(violations ->
assertThatConstraintViolations(violations)
.singleElement()
.hasMessageTemplate("{jakarta.validation.constraints.NotBlank.message}"));When the consumer runs, and what it receives, currently depends on the method. Whether it should be an observer that always sees the validation output or a hook that runs only after the assertion passes, and whether it should always receive an unmodifiable set, is undecided and tracked in #9. Do not build on the differences below; they describe the working tree, not a contract.
| Overload | Consumer runs | On a failing assertion | Set passed |
|---|---|---|---|
isValid(Consumer) |
before the emptiness check | the consumer has already seen the violations | unmodifiable view |
isNotValid(Consumer) |
after the check passes | the consumer never runs | the provider's set |
hasValidProperty(String, Consumer) |
after the check passes | the consumer never runs | the provider's set |
isValidFor(…, Consumer), inherited from AbstractPropertyAssert, behaves like isValid(Consumer); the
executable-validation overloads behave like isNotValid(Consumer). doesNotHaveValidProperty has no consumer
overload (#12).
Anything the consumer throws propagates unchanged; an AssertionError from an assertion made inside it fails the
test as usual.
To capture violations from an assertion that is expected to fail, the "before" overloads are the ones that see them
today — this is how User_ConstraintViolationAssert_Test collects them:
final var violations = new HashSet<ConstraintViolation<User>>();
assertThatThrownBy(() -> assertThatProperty(" ").isValidFor(User.class, "name", violations::addAll))
.isInstanceOf(AssertionError.class);
assertThat(violations).hasSize(1);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