While AssertJ's built-in assertions cover JDK types well, your domain objects benefit from tailored assertions. Instead of repeating assertThat(order.getStatus()).isEqualTo("SHIPPED") everywhere, you can build a OrderAssert class with a method isShipped(). This makes test intent crystal clear and reduces duplication.
Extending AbstractAssert
To create a custom assertion, extend AbstractAssert<SELF, ACTUAL> where SELF is your assertion class (for fluent chaining) and ACTUAL is the type under assertion. Provide a static factory method assertThat(ACTUAL) as the entry point.
Example
package com.logicbig.example;
import org.assertj.core.api.AbstractAssert;
public class PersonAssert extends AbstractAssert<PersonAssert, Person> {
public PersonAssert(Person actual) {
super(actual, PersonAssert.class);
}
public static PersonAssert assertThat(Person actual) {
return new PersonAssert(actual);
}
public PersonAssert isAdult() {
isNotNull();
if (actual.age() < 18) {
failWithMessage("Expected adult but age was %d",
actual.age());
}
return this;
}
public PersonAssert hasName(String name) {
isNotNull();
if (!actual.name().equals(name)) {
failWithMessage("Expected name <%s> but was <%s>",
name,
actual.name());
}
return this;
}
}
Test class
package com.logicbig.example;
import org.junit.jupiter.api.Test;
public class CustomAssertionsExample {
@Test
void myTest() {
Person alice = new Person("Alice", 30);
Person bob = new Person("Bob", 15);
// using custom assertion
PersonAssert.assertThat(alice).isAdult().hasName("Alice");
System.out.println("Alice custom assertion passed");
PersonAssert.assertThat(bob).hasName("Bob");
System.out.println("Bob name check passed");
// chaining custom assertions
PersonAssert.assertThat(alice).isAdult().hasName("Alice");
System.out.println("Chained custom assertions passed");
}
}
Output$ mvn clean test -Dtest=* [INFO] Scanning for projects... [INFO] [INFO] -----------< com.logicbig.example:assertj-custom-assertions >----------- [INFO] Building assertj-custom-assertions 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ assertj-custom-assertions --- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ assertj-custom-assertions --- [INFO] skip non existing resourceDirectory D:\example-projects\assertj\assertj-custom-assertions\src\main\resources [INFO] [INFO] --- compiler:3.11.0:compile (default-compile) @ assertj-custom-assertions --- [INFO] No sources to compile [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ assertj-custom-assertions --- [INFO] skip non existing resourceDirectory D:\example-projects\assertj\assertj-custom-assertions\src\test\resources [INFO] [INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ assertj-custom-assertions --- [INFO] Changes detected - recompiling the module! :source [INFO] Compiling 3 source files with javac [debug target 17] to target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ assertj-custom-assertions --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.CustomAssertionsExample Alice custom assertion passed Bob name check passed Chained custom assertions passed [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.062 s -- in com.logicbig.example.CustomAssertionsExample [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.215 s [INFO] Finished at: 2026-03-03T14:10:34+08:00 [INFO] ------------------------------------------------------------------------
Conclusion
The output confirms that domain-specific assertion methods work correctly. By encapsulating the assertion logic in PersonAssert, the test code reads like a specification: assertThat(person).isAdult().hasName("Alice") is immediately understandable to any team member, regardless of AssertJ knowledge.
Example ProjectDependencies and Technologies Used: - assertj-core 3.27.7 (Rich and fluent assertions for testing in Java)
- junit-jupiter-engine 6.0.2 (Module "junit-jupiter-engine" of JUnit)
- JDK 17
- Maven 3.9.11
|