AssertJ

Javaunit testingassertionsfluent APItest readabilityJUnitTestNGBDD

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: then syntax 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

  1. Excellent readability: Fluent API resembling natural language makes test intentions clear
  2. IDE friendly: Code completion dramatically improves development efficiency
  3. Rich assertions: Specialized assertion methods for various data types
  4. Superior error messages: Detailed messages that make failure causes immediately apparent
  5. Low learning curve: Easy to learn with unified API patterns
  6. Flexibility: Can be combined with existing test frameworks like JUnit and TestNG
  7. Active development: Continuous feature additions and bug fixes
  8. Chainable: Multiple assertions can be chained together

Disadvantages

  1. External dependency: Requires additional library dependency
  2. Performance: Slight overhead compared to basic JUnit assertions
  3. Learning time: Time required to understand all features due to richness
  4. Java only: Cannot be used with other programming languages
  5. 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();
}