AssertJ
Unit Testing Tool
AssertJ
Overview
AssertJ is a fluent assertion library for Java that provides a rich set of assertions and truly helpful error messages, dramatically improving test code readability and maintainability. Designed to be super easy to use within your favorite IDE, it can be used with JUnit, TestNG, or any other test framework, offering strongly-typed rich assertions with meaningful error messages.
Details
AssertJ was developed to overcome the limitations of traditional JUnit assertion methods. The latest version as of 2024 is 3.27.2, requiring Java 8 or higher. It features a fluent API starting from a single entry point assertThat(), designed to maximize IDE code completion capabilities.
Key features of AssertJ:
- Fluent API: Intuitive description using
assertThat(actual).methodChaining - Rich assertions: Support for various types including strings, collections, dates, files, exceptions
- Detailed error messages: Clear and understandable messages when tests fail
- IDE integration: Efficient test creation through code completion support
- Soft assertions: Collect multiple assertion errors
- Recursive comparison: Deep object comparison capabilities
- Custom assertions: Ability to create custom assertions
- BDD style:
thensyntax suitable for behavior-driven development
AssertJ provides multiple modules:
- Core: Assertions for JDK types (String, Iterable, Stream, Path, File, Map, etc.)
- Guava: Assertions for Guava types (Multimap, Optional, etc.)
- Joda Time: Assertions for Joda Time types
- Neo4J: Assertions for Neo4J types
- DB: Assertions for relational database types
Advantages and Disadvantages
Advantages
- Excellent readability: Fluent API resembling natural language makes test intentions clear
- IDE friendly: Code completion dramatically improves development efficiency
- Rich assertions: Specialized assertion methods for various data types
- Superior error messages: Detailed messages that make failure causes immediately apparent
- Low learning curve: Easy to learn with unified API patterns
- Flexibility: Can be combined with existing test frameworks like JUnit and TestNG
- Active development: Continuous feature additions and bug fixes
- Chainable: Multiple assertions can be chained together
Disadvantages
- External dependency: Requires additional library dependency
- Performance: Slight overhead compared to basic JUnit assertions
- Learning time: Time required to understand all features due to richness
- Java only: Cannot be used with other programming languages
- Method count: Large number of methods can sometimes cause confusion due to choice overload
Reference Pages
Usage Examples
Basic Setup
Maven Dependencies
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.27.2</version>
<scope>test</scope>
</dependency>
Gradle Dependencies
testImplementation("org.assertj:assertj-core:3.27.2")
Basic Import
import static org.assertj.core.api.Assertions.*;
Basic Assertions
Number Assertions
@Test
void numberAssertions() {
assertThat(42)
.isEqualTo(42)
.isNotEqualTo(41)
.isGreaterThan(40)
.isLessThan(50)
.isBetween(40, 50)
.isPositive()
.isNotZero();
assertThat(3.14)
.isCloseTo(3.1, within(0.1))
.isGreaterThan(3.0);
}
String Assertions
@Test
void stringAssertions() {
assertThat("Hello World")
.isNotEmpty()
.hasSize(11)
.contains("World")
.startsWith("Hello")
.endsWith("World")
.doesNotContain("Java")
.matches("Hello.*")
.isEqualToIgnoringCase("hello world");
}
Boolean Assertions
@Test
void booleanAssertions() {
assertThat(true).isTrue();
assertThat(false).isFalse();
String value = null;
assertThat(value).isNull();
String notNull = "test";
assertThat(notNull).isNotNull();
}
Collection Assertions
List Assertions
@Test
void listAssertions() {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
assertThat(fruits)
.hasSize(3)
.contains("apple")
.containsExactly("apple", "banana", "cherry")
.containsExactlyInAnyOrder("cherry", "apple", "banana")
.doesNotContain("orange")
.startsWith("apple")
.endsWith("cherry");
}
Map Assertions
@Test
void mapAssertions() {
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
assertThat(map)
.hasSize(2)
.containsKey("key1")
.containsValue("value1")
.containsEntry("key1", "value1")
.doesNotContainKey("key3");
}
Exception Assertions
Exception Verification
@Test
void exceptionAssertions() {
// Verify that exception is thrown
assertThatThrownBy(() -> {
throw new IllegalArgumentException("Invalid argument");
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid argument")
.hasMessageContaining("Invalid");
// Verify that no exception is thrown
assertThatCode(() -> {
// Normal processing
String result = "test".toUpperCase();
}).doesNotThrowAnyException();
}
Specific Exception Type Verification
@Test
void specificExceptionAssertions() {
assertThatIllegalArgumentException().isThrownBy(() -> {
throw new IllegalArgumentException("error");
}).withMessage("error");
assertThatNullPointerException().isThrownBy(() -> {
String str = null;
str.length();
});
}
Object Assertions
Field Comparison
public class Person {
private String name;
private int age;
// constructors, getters, setters
}
@Test
void objectAssertions() {
Person person = new Person("John", 30);
assertThat(person)
.extracting("name", "age")
.containsExactly("John", 30);
assertThat(person)
.extracting(Person::getName)
.isEqualTo("John");
}
Recursive Comparison
@Test
void recursiveComparison() {
Person person1 = new Person("John", 30);
Person person2 = new Person("John", 30);
// Compare fields recursively
assertThat(person1)
.usingRecursiveComparison()
.isEqualTo(person2);
// Ignore specific fields
assertThat(person1)
.usingRecursiveComparison()
.ignoringFields("age")
.isEqualTo(person2);
}
Soft Assertions
Collecting Multiple Errors
@Test
void softAssertions() {
SoftAssertions softly = new SoftAssertions();
String name = "John";
int age = 25;
softly.assertThat(name).isEqualTo("Jane"); // Fails
softly.assertThat(age).isEqualTo(30); // Fails
softly.assertThat(name).isNotEmpty(); // Passes
// Execute all assertions and report errors collectively
softly.assertAll();
}
@Test
void softAssertionsWithJUnit5() {
assertSoftly(softly -> {
softly.assertThat("John").isEqualTo("Jane");
softly.assertThat(25).isEqualTo(30);
softly.assertThat("John").isNotEmpty();
});
}
Date and File Assertions
Date Assertions
@Test
void dateAssertions() {
Date now = new Date();
Date yesterday = Date.from(Instant.now().minus(1, ChronoUnit.DAYS));
assertThat(now)
.isAfter(yesterday)
.isInThePast() // Before actual current time
.isCloseTo(new Date(), 1000); // Within 1 second
}
File Assertions
@Test
void fileAssertions() throws IOException {
File file = new File("test.txt");
file.createNewFile();
assertThat(file)
.exists()
.isFile()
.canRead()
.canWrite()
.hasExtension("txt");
file.delete();
}
Custom Assertions
Creating Custom Assertions
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 hasName(String name) {
isNotNull();
if (!Objects.equals(actual.getName(), name)) {
failWithMessage("Expected name to be <%s> but was <%s>",
name, actual.getName());
}
return this;
}
public PersonAssert isAdult() {
isNotNull();
if (actual.getAge() < 18) {
failWithMessage("Expected person to be adult but was <%d> years old",
actual.getAge());
}
return this;
}
}
@Test
void customAssertions() {
Person person = new Person("John", 25);
PersonAssert.assertThat(person)
.hasName("John")
.isAdult();
}
BDD Style
Using then Syntax
import static org.assertj.core.api.BDDAssertions.*;
@Test
void bddStyleAssertions() {
// Given
String input = "hello";
// When
String result = input.toUpperCase();
// Then
then(result).isEqualTo("HELLO");
then(result).isNotEqualTo("hello");
}
Stream and Optional Assertions
Stream API Integration
@Test
void streamAssertions() {
List<String> names = Arrays.asList("John", "Jane", "Bob");
assertThat(names.stream())
.filteredOn(name -> name.startsWith("J"))
.hasSize(2)
.containsExactly("John", "Jane");
assertThat(names)
.filteredOn(name -> name.length() > 3)
.extracting(String::toUpperCase)
.containsExactly("JOHN", "JANE");
}
Optional Assertions
@Test
void optionalAssertions() {
Optional<String> optional = Optional.of("value");
Optional<String> empty = Optional.empty();
assertThat(optional)
.isPresent()
.hasValue("value")
.get().isEqualTo("value");
assertThat(empty).isEmpty();
}