A test that checks some properties of a model says nothing about the others. When a property is added to the model later, the existing test passes and the new property stays unasserted. The completeness check closes that gap: when you turn it on, the assertion fails unless every property of the type is either checked or ignored.
A property counts as addressed when a check names it, or when you name it with ignoringX() to say that you decided not to check it. Ignoring is visible in the test, so the decision is reviewed with the test.
The check is off by default. It is opt-in per assertion, so a test that does not ask for it keeps the behaviour of 5.x.
Hamcrest
Call checkingAllProperties() on the matcher. Every property must be checked with withX or named with ignoringX(). A resetX call removes both, so a reset property counts as neither:
assertThat(
ADA,
isCustomer()
.withName("Ada")
.withAddress(isAddress().withCity("Paris"))
.ignoringActive()
.checkingAllProperties());
When a property was neither checked nor ignored, the failure names it with its property path. The message below is the golden output of the runtime test BeanPropertyMatcherTest for a booking whose customer.phone and customer.address were neither checked nor ignored; customer.phone is a nested property:
Expected: Booking with 2 property checks, every property checked or ignored
but: id: expected "b-2" but was "b-1"
neither checked nor ignored: customer.phone, customer.address
The message is the last thing that fails. Value mismatches come first, so a wrong value is reported as a wrong value, and the check names only the properties that remain.
A matcher made with isCustomerLike(reference) turns the check on as well. The reference object sets every property, and withX, resetX and ignoringX override single properties of it.
AssertJ
The AssertJ backend has two forms. The closing call hasAllPropertiesChecked() checks the whole chain of the assertion. Each hasX, hasXSatisfying and boolean isX or isNotX counts as a check, and ignoringX() counts as ignoring:
CustomerAssert.assertThat(ADA)
.hasName("Ada")
.hasAddressSatisfying(address -> AddressAssert.assertThat(address).hasCity("Paris"))
.ignoringActive()
.hasAllPropertiesChecked();
The scope form satisfiesCheckingAllProperties(..) runs the check only inside its lambda. It counts what the lambda addresses and nothing that comes before it in the chain. Use it when only one part of a larger assertion should be complete:
CustomerAssert.assertThat(ADA)
.satisfiesCheckingAllProperties(
customer ->
customer.hasName("Ada")
.hasAddressSatisfying(
address ->
AddressAssert.assertThat(address)
.hasCity("Paris"))
.isActive());
A failure names the properties in a sentence of this form: Expecting every property of Order to be checked or ignored, but these were neither: tags. The check of a nested assertion names the path of the nested type, so the same sentence for the customer of an order starts with Order.customer. This sentence is the one the AssertJ end-to-end test CompletenessCheckTest asserts.
Navigation with extracting(…, as(X)) or first(as(X)) starts a new assertion for a new subject. That assertion has its own set of addressed properties, so checks made before the navigation do not count for it.
Kotlin
The Kotlin DSL has the same check. Inside a scope, checkingAllProperties() turns it on and ignoringX() names a property you leave out:
assertThat(ada, isCustomer {
checkingAllProperties()
name("Ada")
address { city("Paris") }
ignoringActive()
})
The DSL delegates to the Java matcher, so the failure message is the one of the Hamcrest backend. The Kotlin chapter shows the complete setup.
Which properties count
Every generated matcher also lists class, from getClass(), so withClass(..) and ignoringClass() exist. The completeness check does not count class: you never need to ignore it. Enum types also get declaringClass, and whether the check should count it is still open.
Next steps
-
Hamcrest backend and AssertJ backend for the rest of the generated API.
-
Mismatch messages for the full shape of the messages.
-
Kotlin for the DSL in a Kotlin project.