Repository navigation
Executable Validation
Method and constructor constraints are checked through Validator.forExecutables(), the ExecutableValidator.
Nothing is invoked: the test supplies the argument values or the return value, and the assertion validates them.
Method validation needs the instance the method belongs to, so it hangs off the bean assertion.
Constructor validation has no instance yet, so it has an assertion of its own whose actual is the Constructor.
The fixture used below, from example/service:
public class Greeter {
@NotNull
@Named // a return-value constraint: the created Greeter has a non-blank name
public Greeter(@NotBlank final String name) {
this.name = name;
}
@NotBlank
public String greet(@NotBlank final String whom, @Positive final int times) {
return (name + " greets " + whom + ' ').repeat(Math.max(times, 1));
}
public String getName() {
return name;
}
@NotBlank
private final String name;
}| Method |
ExecutableValidator call |
Passes when |
|---|---|---|
hasValidParameters(Method, Object...) |
validateParameters(actual, method, values, groups) |
no violation |
hasValidParameters(Method, Object[], Consumer) |
same | no violation |
doesNotHaveValidParameters(Method, Object...) |
same | at least one violation |
hasValidReturnValue(Method, Object) |
validateReturnValue(actual, method, value, groups) |
no violation |
hasValidReturnValue(Method, Object, Consumer) |
same | no violation |
doesNotHaveValidReturnValue(Method, Object) |
same | at least one violation |
final Method greet = Greeter.class.getMethod("greet", String.class, int.class);
final var greeter = new Greeter("Jane");
assertThatBean(greeter).hasValidParameters(greet, "world", 1);
assertThatBean(greeter).doesNotHaveValidParameters(greet, " ", 0);
assertThatBean(greeter).hasValidReturnValue(greet, "Jane greets world");
assertThatBean(greeter).doesNotHaveValidReturnValue(greet, " ");With a consumer, the argument values are passed as an explicit Object[], since the consumer follows them:
assertThatBean(greeter).hasValidParameters(greet, new Object[]{"world", 1},
violations -> assertThat(violations).isEmpty());The bean must not be null (an AssertionError), and neither may the Method (a NullPointerException).
assertThatConstructor(Constructor<? extends T>) returns an AbstractConstructorAssert<?, T>:
| Method |
ExecutableValidator call |
Passes when |
|---|---|---|
hasValidParameters(Object...) |
validateConstructorParameters(actual, values, groups) |
no violation |
hasValidParameters(Object[], Consumer) |
same | no violation |
doesNotHaveValidParameters(Object...) |
same | at least one violation |
hasValidReturnValue(T createdObject) |
validateConstructorReturnValue(actual, createdObject, groups) |
no violation |
hasValidReturnValue(T, Consumer) |
same | no violation |
doesNotHaveValidReturnValue(T) |
same | at least one violation |
final Constructor<Greeter> constructor = Greeter.class.getConstructor(String.class);
assertThatConstructor(constructor).hasValidParameters("Jane");
assertThatConstructor(constructor).doesNotHaveValidParameters(new Object[]{" "});
assertThatConstructor(constructor).hasValidReturnValue(new Greeter("Jane"));
final var blank = new Greeter(" "); // parameter constraints do not run at construction time
assertThatConstructor(constructor).doesNotHaveValidReturnValue(blank);Two things about constructor return values follow from the specification rather than from this library, and the tests pin both:
-
A
nullcreated object is not a constraint violation. The provider rejects it with anIllegalArgumentExceptionbefore any constraint runs, and the assertion lets that propagate; it does not turn it into an assertion failure. A@NotNullon a constructor can therefore never fail — which is why the fixture has the custom@Namedconstraint. -
Standard constraints on the created object are reached through
@Valid. A constructor carrying@NotNull @Validcascades into the new instance, so a@NotBlankfield of it is checked:public static class Account { @NotNull @Valid public Account(final String id) { this.id = id; } @NotBlank private final String id; } assertThatConstructor(Account.class.getConstructor(String.class)) .doesNotHaveValidReturnValue(new Account(" "));
Both assertions extend AbstractValidationAssert, so targetingGroups, usingValidator and
usingValidatorFactory apply as on Validating Beans and Validator Lifecycle.
A failure names what was validated, in the same shape for methods and constructors:
The set of constraint violations resulted while validating
actual: …Greeter@…
for the
parameters of
method: public java.lang.String …Greeter.greet(java.lang.String,int)
targeting
groups: [interface jakarta.validation.groups.Default]
expected to be empty but contains 1 element(s)
-> …
The set of constraint violations resulted while validating
return value of
constructor: public …Greeter(java.lang.String)
targeting
groups: [interface jakarta.validation.groups.Default]
expected to be not empty but empty
The consumer overloads run the consumer only after the assertion has passed, and pass the provider's set as is. That
differs from isValid(Consumer) and is open in #9. The
negative methods (doesNotHaveValidParameters, doesNotHaveValidReturnValue) have no consumer overload, so a test
that expects an invalid argument cannot see which constraint failed through this API today
(#12); call the ExecutableValidator directly and use
Inspecting Violations on the result.
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