1. AssertJ Overview
AssertJ is composed of several modules:
-
A core module to provide assertions for JDK types (String, Iterable, Stream, Path, File, Map, …)
-
A Guava module to provide assertions for Guava types (Multimap, Optional, …)
-
A Joda Time module to provide assertions for Joda Time types (DateTime, LocalDateTime)
-
A Neo4J module to provide assertions for Neo4J types (Path, Node, Relationship, …)
-
A DB module to provide assertions for relational database types (Table, Row, Column, …)
-
A Swing module provides a simple and intuitive API for functional testing of Swing user interfaces
2. AssertJ Core
The goal of this document is to provide comprehensive reference documentation for programmers writing tests assertions with AssertJ.
2.1. What is AssertJ Core?
AssertJ is a Java library that provides a rich set of assertions and truly helpful error messages, improves test code readability, and is designed to be super easy to use within your favorite IDE.
https://www.javadoc.io/doc/org.assertj/assertj-core/3.27.7 is the latest version of AssertJ Core Javadoc, each assertion is explained, most of them with code examples so be sure to check it if you want to know what a specific assertion does.
Here are a few examples of AssertJ assertions:
// entry point for all assertThat methods and utility methods (e.g. entry)
import static org.assertj.core.api.Assertions.*;
// basic assertions
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
// chaining string specific assertions
assertThat(frodo.getName()).startsWith("Fro")
.endsWith("do")
.isEqualToIgnoringCase("frodo");
// collection specific assertions (there are plenty more)
// in the examples below fellowshipOfTheRing is a List<TolkienCharacter>
assertThat(fellowshipOfTheRing).hasSize(9)
.contains(frodo, sam)
.doesNotContain(sauron);
// as() is used to describe the test and will be shown before the error message
assertThat(frodo.getAge()).as("check %s's age", frodo.getName()).isEqualTo(33);
// exception assertion, standard style ...
assertThatThrownBy(() -> { throw new Exception("boom!"); }).hasMessage("boom!");
// ... or BDD style
Throwable thrown = catchThrowable(() -> { throw new Exception("boom!"); });
assertThat(thrown).hasMessageContaining("boom");
// using the 'extracting' feature to check fellowshipOfTheRing character's names
assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName)
.doesNotContain("Sauron", "Elrond");
// extracting multiple values at once grouped in tuples
assertThat(fellowshipOfTheRing).extracting("name", "age", "race.name")
.contains(tuple("Boromir", 37, "Man"),
tuple("Sam", 38, "Hobbit"),
tuple("Legolas", 1000, "Elf"));
// filtering a collection before asserting
assertThat(fellowshipOfTheRing).filteredOn(character -> character.getName().contains("o"))
.containsOnly(aragorn, frodo, legolas, boromir);
// combining filtering and extraction (yes we can)
assertThat(fellowshipOfTheRing).filteredOn(character -> character.getName().contains("o"))
.containsOnly(aragorn, frodo, legolas, boromir)
.extracting(character -> character.getRace().getName())
.contains("Hobbit", "Elf", "Man");
// and many more assertions: iterable, stream, array, map, dates, path, file, numbers, predicate, optional ...
2.2. Getting Help
Ask AssertJ related questions on Stack Overflow.
2.3. Contributing to this guide
You are very welcome to suggest or contribute improvements to this guide, that’s one great way to give back to open source projects!
The repository containing the guide is https://github.com/assertj/doc, you can create a new issue, submit a pull request. Et voila!
This guide is written with the awesome Asciidoctor which makes it easy to improve.
2.4. Quick start
This guide is for the AssertJ Core module.
2.4.2. Get AssertJ Core
The AssertJ Core artifact can be included directly using its dependency metadata or indirectly via the Bill of Materials POM.
Maven
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.27.7</version>
<scope>test</scope>
</dependency>
Other build tools
Check this page to find the relevant Assertj Core dependency declaration.
Spring Boot
Spring Boot provides automatic support for managing the version of AssertJ used in your project.
In addition, the spring-boot-starter-test artifact automatically includes testing libraries such as JUnit Jupiter, AssertJ Core, Mockito, etc.
If you need to override the version of a dependency used in your Spring Boot application, you have to override the exact name of the version property defined in the BOM used by the Spring Boot plugin.
For example, the name of the AssertJ version property in Spring Boot is assertj.version.
The mechanism for changing a dependency version is documented for both Maven and Gradle.
With Maven, you can override the AssertJ version by including the following in your pom.xml file.
<properties>
<assertj.version>3.27.7</assertj.version>
</properties>
With Gradle, you can override the AssertJ version by including the following in your build.gradle file.
ext['assertj.version'] = '3.27.7'
2.4.3. Use Assertions class entry point
The Assertions class is the only class you need to start using AssertJ, it provides all the methods you need.
Alternatively your test class can implement WithAssertions to access the same methods.
One Assertions static import to rule them all …
import static org.assertj.core.api.Assertions.*;
... or many if you prefer:
import static org.assertj.core.api.Assertions.assertThat; // main one
import static org.assertj.core.api.Assertions.atIndex; // for List assertions
import static org.assertj.core.api.Assertions.entry; // for Map assertions
import static org.assertj.core.api.Assertions.tuple; // when extracting several properties at once
import static org.assertj.core.api.Assertions.fail; // use when writing exception tests
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; // idem
import static org.assertj.core.api.Assertions.filter; // for Iterable/Array assertions
import static org.assertj.core.api.Assertions.offset; // for floating number assertions
import static org.assertj.core.api.Assertions.anyOf; // use with Condition
import static org.assertj.core.api.Assertions.contentOf; // use with File assertions
Alternative entry points
AssertJ provides other entry points class, notably the WithAssertions interface and BDDAssertions for BDD style assertions that replace assertThat by then.
WithAssertions example:
import org.assertj.core.api.WithAssertions;
public class WithAssertionsExamples extends AbstractAssertionsExamples implements WithAssertions {
// the data used are initialized in AbstractAssertionsExamples.
@Test
public void withAssertions_examples() {
// assertThat methods come from WithAssertions - no static import needed
assertThat(frodo.age).isEqualTo(33);
assertThat(frodo.getName()).isEqualTo("Frodo").isNotEqualTo("Frodon");
assertThat(frodo).isIn(fellowshipOfTheRing);
assertThat(frodo).isIn(sam, frodo, pippin);
assertThat(sauron).isNotIn(fellowshipOfTheRing);
assertThat(frodo).matches(p -> p.age > 30 && p.getRace() == HOBBIT);
assertThat(frodo.age).matches(p -> p > 30);
}
}
BDDAssertions example:
import static org.assertj.core.api.BDDAssertions.then;
public class BDDAssertionsExamples extends AbstractAssertionsExamples {
// the data used are initialized in AbstractAssertionsExamples.
@Test
public void withAssertions_examples() {
// then methods come from BDDAssertions.then static
then(frodo.age).isEqualTo(33);
then(frodo.getName()).isEqualTo("Frodo").isNotEqualTo("Frodon");
then(frodo).isIn(fellowshipOfTheRing);
then(frodo).isIn(sam, frodo, pippin);
then(sauron).isNotIn(fellowshipOfTheRing);
then(frodo).matches(p -> p.age > 30 && p.getRace() == HOBBIT);
then(frodo.age).matches(p -> p > 30);
}
}
IDE configuration
You can configure your IDE so that when you start typing as and trigger code completion assertThat will show up in the suggested completions.
Eclipse:
-
Go to Window > Preferences > Java > Editor > Content Assist > Favorites > New Type.
-
Enter
org.assertj.core.api.Assertionsand click OK. -
Check that you see
org.assertj.core.api.Assertions.*in Favorites.
Intellij Idea: No special configuration is needed, just start typing asser and then invoke completion (Ctrl-Space) twice.
2.4.4. Use code completion
Type assertThat followed by the object under test and a dot … and any Java IDE code completion will show you all available assertions.
assertThat(objectUnderTest). (1)
| 1 | Use IDE code completion after the dot. |
Example for String assertions:
2.4.5. Javadoc
https://www.javadoc.io/doc/org.assertj/assertj-core/3.27.7 is the latest version of AssertJ Core Javadoc, each assertion is explained, most of them with code examples so be sure to check it if you want to know what a specific assertion does.
2.5. Core assertions guide
This section describes the assertions provided by AssertJ Core and other useful features to get the best of it.
AssertJ Core Javadoc explains each assertion, most of them with code examples so be sure to check it if you want to know what a specific assertion does.
2.5.1. A simple example
Let’s start with a simple example showing a few important things.
import static org.assertj.core.api.Assertions.assertThat; (1)
import org.junit.jupiter.api.Test;
public class SimpleAssertionsExample {
@Test
void a_few_simple_assertions() {
assertThat("The Lord of the Rings").isNotNull() (2) (3)
.startsWith("The") (4)
.contains("Lord") (4)
.endsWith("Rings"); (4)
}
}
| 1 | Statically import org.assertj.core.api.Assertions.assertThat |
| 2 | Pass the object under test as the sole assertThat() parameter |
| 3 | Use code completion to discover and call assertions |
| 4 | Chain as many assertions as you need |
Except for isNotNull which is a base assertion, the other assertions are String specific as our object under test is a String.
2.5.2. Supported type assertions
AssertJ provides assertions specific to the object under test type, the following sections list the supported types grouped by categories.
The provided assertions for each of these types are documented later on.
Common types
BigDecimal |
BigInteger |
CharSequence |
Class |
Date |
File |
Future / CompletableFuture |
InputStream |
Iterable (including any kind of Collection) |
Iterator |
List |
Map |
Object |
Object[] and Object[][] |
|
Path |
Predicate |
Stream |
String |
Throwable / Exception |
Primitive types
Primitive types and their wrapper:
|
Primitive type arrays:
|
Primitive type 2D arrays:
|
Java 8 Temporal types
Instant |
LocalDate |
LocalDateTime |
LocalTime |
OffsetDateTime |
OffsetTime |
ZonedDateTime |
Period |
Atomic types
Atomic basic types:
|
Atomic array types:
|
Atomic reference types:
|
Atomic updater types:
|
Adder types:
|
2.5.3. Assertion description
It is often valuable to describe the assertion performed, especially for boolean assertions where the default error message just complains that it got false instead of true (or vice versa).
You can set such a description with as(String description, Object… args) but remember to do it before calling the assertion otherwise it is simply ignored as a failing assertion breaks the chained calls.
Example of a failing assertion with a description:
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, Race.HOBBIT);
// failing assertion, remember to call as() before the assertion!
assertThat(frodo.getAge()).as("check %s's age", frodo.getName())
.isEqualTo(100);
The error message starts with the given description in [] :
[check Frodo's age] expected:<100> but was:<33>
Printing or consuming description
AssertJ can print each assertion description (when it is set), to do so call Assertions.setPrintAssertionsDescription(true);.
If printing assertion descriptions is not what you need, you can alternatively register a Consumer<Description> that will be called each time a description is set.
Both options are exposed in AssertJ Configuration class.
Example: using a description consumer
// initialize the description consumer
final StringBuilder descriptionReportBuilder = new StringBuilder(String.format("Assertions:%n"));
Consumer<Description> descriptionConsumer = desc -> descriptionReportBuilder.append(String.format("-- %s%n", desc));
// use the description consumer for any following assertions descriptions.
Assertions.setDescriptionConsumer(descriptionConsumer);
// execute some assertions
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, Race.HOBBIT);
assertThat(frodo.getName()).as("check name")
.isEqualTo("Frodo");
assertThat(frodo.getAge()).as("check age")
.isEqualTo(33);
// get the report
String descriptionReport = descriptionReportBuilder.toString();
resulting descriptionReport:
Assertions:
-- check name
-- check age
2.5.4. Overriding error message
AssertJ tries its best to give helpful error messages, but you can always change it with overridingErrorMessage() or withFailMessage().
Example with this failing test:
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, Race.HOBBIT);
TolkienCharacter sam = new TolkienCharacter("Sam", 38, Race.HOBBIT);
// failing assertion, remember to call withFailMessage/overridingErrorMessage before the assertion!
assertThat(frodo.getAge()).withFailMessage("should be %s", frodo)
.isEqualTo(sam);
The error message is:
java.lang.AssertionError: should be TolkienCharacter [name=Frodo, age=33, race=HOBBIT]
Lazy error message overriding
If the error message is expensive to build, use the overloaded methods taking a Supplier<String> instead of a String, the message will only be built if the assertion fails.
Example:
assertThat(player.isRookie()).overridingErrorMessage(() -> "Expecting Player to be a rookie but was not.")
.isTrue();
assertThat(player.isRookie()).withFailMessage(() -> "Expecting Player to be a rookie but was not.")
.isTrue();
2.5.5. Avoiding incorrect usage
There are a few things to keep in mind when using AssertJ to avoid misusing it.
Forgetting to call an assertion
The main trap is to pass the object under test to assertThat() and forget to call an assertion afterward.
This misuse can be detected by multiple static code analysis tools:
-
SpotBugs or FindBugs with the
RV_RETURN_VALUE_IGNORED_INFERREDrule -
SonarQube with the
Assertions should be complete (S2970)rule -
other tools that can evaluate whether calls to methods annotated with the
@CheckReturnValueannotation are done correctly.
Here’s what it looks like in SpotBugs:
The following examples show incorrect AssertJ API usage to avoid!
Bad
// DON'T DO THIS ! It does not assert anything
assertThat(actual.equals(expected));
Good
// DO THIS:
assertThat(actual).isEqualTo(expected);
// OR THIS (less classy but ok):
assertThat(actual.equals(expected)).isTrue();
Bad
// DON'T DO THIS ! It does not assert anything and passes
assertThat(1 == 2);
Good
// DO THIS: (fails as expected)
assertThat(1).isEqualTo(2);
// OR THIS (less classy but ok):
assertThat(1 == 2).isTrue();
Calling as() after the assertion
Describing an assertion must be done before calling the assertion.
Otherwise it is ignored as a failing assertion will prevent the call to as().
Bad
// DON'T DO THIS ! as/describedAs have no effect after the assertion
assertThat(actual).isEqualTo(expected).as("description");
assertThat(actual).isEqualTo(expected).describedAs("description");
Good
// DO THIS: use as/describedAs before the assertion
assertThat(actual).as("description").isEqualTo(expected);
assertThat(actual).describedAs("description").isEqualTo(expected);
Calling withFailMessage/overridingErrorMessage after the assertion
Setting an error message must be done before calling the assertion.
Otherwise it is ignored as a failing assertion will prevent the call to withFailMessage() / overridingErrorMessage().
Bad
// DON'T DO THIS ! overridingErrorMessage/withFailMessage have no effect after the assertion
assertThat(actual).isEqualTo(expected).overridingErrorMessage("custom error message");
assertThat(actual).isEqualTo(expected).withFailMessage("custom error message");
Good
// DO THIS: use overridingErrorMessage/withFailMessage before the assertion
assertThat(actual).overridingErrorMessage("custom error message").isEqualTo(expected);
assertThat(actual).withFailMessage("custom error message").isEqualTo(expected);
Setting a comparator after the assertion
Setting comparators must be done before calling the assertion.
Otherwise it is ignored as a failing assertion will prevent the call to usingComparator() / usingElementComparator().
Bad
// DON'T DO THIS ! Comparator is not used
assertThat(actual).isEqualTo(expected).usingComparator(new CustomComparator());
Good
// DO THIS:
assertThat(actual).usingComparator(new CustomComparator()).isEqualTo("a");
2.5.6. Configuring AssertJ
This section describes the different ways to configure AssertJ, either by setting configuration properties individually or globally using the Configuration class.
To be effective the configuration changes must be applied before the tests are executed, depending on the scope of the tests this means different things:
-
For a single test: change the configuration in the test and revert it in the
@AfterEachmethod (JUnit 5). -
For all tests in a class: change the configuration in the
@BeforeAllmethod and revert the changes in the@AfterAllmethod (JUnit 5). -
To change the configuration before any tests, you can use these options:
-
write a JUnit 5 extension implementing
BeforeAllCallback. -
register your own
Configurationsubclass and let AssertJ discover it automagically.
-
Configuring single properties
The Assertions class provides static methods to change each configuration properties.
Assertions.setAllowComparingPrivateFields(true);
Assertions.setAllowExtractingPrivateFields(false);
Assertions.setExtractBareNamePropertyMethods(false);
Assertions.setLenientDateParsing(true);
Assertions.setMaxElementsForPrinting(100);
Assertions.setMaxLengthForSingleLineDescription(250);
Assertions.setRemoveAssertJRelatedElementsFromStackTrace(true);
Assertions.useRepresentation(myRepresentation);
Assertions.registerCustomDateFormat(myCustomDateFormat);
Assertions.setPrintAssertionsDescription(true);
Assertions.setConsumerDescription(description -> writeToFile(description, report));
Representation
This property allows you to register a Representation to control the way AssertJ formats the different types displayed in the assertion error messages.
Consult the Controlling type formatting chapter for details.
Defaults to StandardRepresentation.
AllowComparingPrivateFields
Globally sets whether the use of private fields is allowed for field/property by field/property comparison. Defaults to true.
AllowExtractingPrivateFields
Globally sets whether the AssertJ extracting capability should be allowed to extract private fields. Defaults to true.
ExtractBareNamePropertyMethods
Globally sets whether the AssertJ extracting capability considers bare-named property methods like String name(). Defaults to true.
LenientDateParsing
Specify whether or not date/time parsing is to be lenient for AssertJ default date formats. With lenient parsing, the parser may use heuristics to interpret inputs that do not precisely match this object’s format. With strict parsing, inputs must match this object’s format.
Custom DateFormat
In addition to the default date formats, you can register some custom ones that AssertJ will use in date assertions (see also Assertions.registerCustomDateFormat).
Note that custom date formats take precedence over default ones.
MaxElementsForPrinting
In error messages, sets the threshold for how many elements from one iterable/array/map will be included in the description. Defaults to 1000.
The point of this property is to avoid printing iterable/array/map with too many elements in error messages.
MaxLengthForSingleLineDescription
In error messages, sets the threshold when iterable/array formatting will be on one line (if their String description is less than this parameter) or it will be formatted with one element per line. Defaults to 80.
Example:
String[] greatBooks = array("A Game of Thrones", "The Lord of the Rings", "Assassin's Apprentice");
this array is formatted on one line as its length < 80:
["A Game of Thrones", "The Lord of the Rings", "Assassin's Apprentice"]
Whereas this array …
String[] greatBooks = array("A Game of Thrones", "The Lord of the Rings", "Assassin's Apprentice", "Guards! Guards! (Discworld)");
... is formatted on multiple lines with one element per line:
["A Game of Thrones",
"The Lord of the Rings",
"Assassin's Apprentice",
"Guards! Guards! (Discworld)"]
AssertJ Configuration
Since 3.13.0, AssertJ exposes a org.assertj.core.configuration.Configuration object providing access to all AssertJ globally configurable properties.
You can create an instance of org.assertj.core.configuration.Configuration and change indivual properties through setters or create your own custom configuration by inheriting from it and overriding the methods to change the default behavior as in the CustomConfiguration example below.
Your configuration will be effective once you call Configuration.apply() or Configuration.applyAndDisplay().
|
Example:
Configuration configuration = new Configuration();
configuration.setBareNamePropertyExtraction(false);
configuration.setComparingPrivateFields(false);
configuration.setExtractingPrivateFields(false);
configuration.setLenientDateParsing(true);
configuration.setMaxElementsForPrinting(1001);
configuration.setMaxLengthForSingleLineDescription(81);
configuration.setRemoveAssertJRelatedElementsFromStackTrace(false);
// don't forget to apply it!
configuration.applyAndDisplay();
Printing the above configuration produces the following output:
Applying configuration org.assertj.core.configuration.Configuration
- representation .................................. = BinaryRepresentation
- comparingPrivateFieldsEnabled ................... = false
- extractingPrivateFieldsEnabled .................. = true
- bareNamePropertyExtractionEnabled ............... = false
- lenientDateParsingEnabled ....................... = true
- additional date formats ......................... = [yyyy_MM_dd, yyyy|MM|dd]
- maxLengthForSingleLineDescription ............... = 150
- maxElementsForPrinting .......................... = 2000
- removeAssertJRelatedElementsFromStackTraceEnabled = true
Automagic configuration discovery
This section describes a way to register an AssertJ Configuration without using any test framework hooks like BeforeAllCallback.
Follow the steps below to register your Configuration as an SPI:
-
Create your own configuration inheriting from
org.assertj.core.configuration.Configuration -
Create a file named
org.assertj.core.configuration.Configurationin aMETA-INF/servicesdirectory -
Make sure
META-INF/services/is in the runtime classpath, usually putting it insrc/test/resourceswill do. -
Put the fully qualified class name of your
Configurationinservices/org.assertj.core.configuration.Configuration.
This is all you have to do, AssertJ will pick up the Configuration automatically and display it at the first interaction with AssertJ.
Here’s an example of a custom configuration class:
package example.core;
import static org.assertj.core.presentation.BinaryRepresentation.BINARY_REPRESENTATION;
import static org.assertj.core.util.Lists.list;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import org.assertj.core.configuration.Configuration;
import org.assertj.core.presentation.Representation;
class CustomConfiguration extends Configuration {
private static final SimpleDateFormat DATE_FORMAT1 = new SimpleDateFormat("yyyy_MM_dd");
private static final SimpleDateFormat DATE_FORMAT2 = new SimpleDateFormat("yyyy|MM|dd");
// we keep the default behavior for extractingPrivateFieldsEnabled since it is not overridden
@Override
public Representation representation() {
return BINARY_REPRESENTATION;
}
@Override
public boolean bareNamePropertyExtractionEnabled() {
return false;
}
@Override
public boolean comparingPrivateFieldsEnabled() {
return false;
}
@Override
public boolean lenientDateParsingEnabled() {
return true;
}
@Override
public List<DateFormat> additionalDateFormats() {
return list(DATE_FORMAT1, DATE_FORMAT2);
}
@Override
public int maxElementsForPrinting() {
return 2000;
}
@Override
public int maxLengthForSingleLineDescription() {
return 150;
}
}
With this custom configuration, the content of META-INF/services/org.assertj.core.configuration.Configuration must be:
example.core.CustomConfiguration
Printing the CustomConfiguration shows:
Applying configuration example.core.CustomConfiguration
- representation .................................. = BinaryRepresentation
- comparingPrivateFieldsEnabled ................... = false
- extractingPrivateFieldsEnabled .................. = true
- bareNamePropertyExtractionEnabled ............... = false
- lenientDateParsingEnabled ....................... = true
- additionnal date formats ........................ = [yyyy_MM_dd, yyyy|MM|dd]
- maxLengthForSingleLineDescription ............... = 150
- maxElementsForPrinting .......................... = 2000
- removeAssertJRelatedElementsFromStackTraceEnabled = true
2.5.7. Controlling type formatting
Assertions error messages use a Representation to format the different types involved. There are multiple ways of registering a custom Representation for assertions:
-
Changing the default global
Representationby callingAssertions.useRepresentation(myRepresentation)- see Changing the default global scope custom representation -
Changing the
Representationper assertion withassertThat(actual).withRepresentation(myRepresentation)- see Per assertion scope custom representation -
Globally registering a
Configurationthat specifies theRepresentationto use - see AssertJ global configuration. -
Registering multiple fine grained representations that only defines representation of their custom types.
Let’s go over these different options with a custom Representation.
Creating a custom Representation
An example of a custom Representation:
// dummy class
private class Example {}
public class CustomRepresentation extends StandardRepresentation { (1)
// override fallbackToStringOf to handle Example formatting
@Override
public String fallbackToStringOf(Object o) { (2)
if (o instanceof Example) return "Example";
// fallback to default formatting.
return super.fallbackToStringOf(o);
}
// override a predefined type formatting: String
@Override
protected String toStringOf(String str) { (3)
return "$" + str + "$";
}
}
| 1 | Extends org.assertj.core.presentation.StandardRepresentation to get AssertJ default representation. |
| 2 | Override fallbackToStringOf and handle your specific types before falling back to the default formatting. |
| 3 | Change a predefined type formatting by overriding the toStringOf method that takes it as a parameter. |
Let’s see the above custom representation in action when representing Example or String instances.
This assertion fails …
assertThat(new Example()).isNull();
…with the following error:
expected:<[null]> but was:<[Example]>
This one fails …
// this one fails ...
assertThat("foo").startsWith("bar");
…with the following error:
Expecting:
<$foo$>
to start with:
<$bar$>
Changing the default global scope custom representation
You only have to register CustomRepresentation once but need to do it before executing any tests, for the tests executed before that, AssertJ will use the default representation.
// to call before executing tests
Assertions.useRepresentation(new CustomRepresentation());
Consider writing a JUnit 5 extension implementing BeforeAllCallback to make sure the representation is set once for all before any test is executed.
Per assertion scope custom representation
Follow this approach if you want to use a specific representation for a single assertion only.
Example with the failing assertions used before:
Representation customRepresentation = new CustomRepresentation();
// this assertion fails ...
assertThat(new Example()).withRepresentation(customRepresentation)
.isNull();
assertThat("foo").withRepresentation(customRepresentation)
.startsWith("bar");
Registering multiple fine-grained representations
Since 3.22.0 AssertJ allows registering multiple representations (one per jar).
The typical use case is for different domain-specific libraries to be able to independently register Representation implementations for their specific domain objects.
|
In case different representations can represent the same type, the one with the highest priority wins. |
Let’s take a concrete example where we have two domain specific libraries: Lotr and star wars and a project that uses them both.
The Lotr library is composed of an Hobbit class and a specific representation for it, note that LotrRepresentation represents Hobbits starting with HOBBIT unlike Hobbit toString method:
package org.assertj.example.lotr;
public class Hobbit {
public String name;
public String age;
@Override
public String toString() {
return format("Hobbit [name=%s, age=%s]", name, age);
}
}
public class LotrRepresentation implements Representation {
@Override
public String toStringOf(Object object) {
if (object instanceof Hobbit) {
Hobbit hobbit = (Hobbit) object;
return String.format("HOBBIT [name=%s, age=%s]", hobbit.name, hobbit.age);
}
return null;
}
// only needed if another library was to represent Hobbit, in this case the one with highest priority wins
@Override
public int getPriority() {
return 5;
}
}
LotrRepresentation is registered by creating a META-INF/services/org.assertj.core.presentation.Representation file that contain org.assertj.example.lotr.LotrRepresentation, the file must be available in the classpath (typically by putting it in src/main/resources it will end up in the library jar).
Similarly the star wars library defines a Jedi and a StarWarsRepresentation:
package org.assertj.example.starwars;
public class Jedi {
public String name;
public String age;
@Override
public String toString() {
return format("Jedi [name=%s, age=%s]", name, age);
}
}
public class StarWarsRepresentation implements Representation {
@Override
public String toStringOf(Object object) {
if (object instanceof Jedi) {
Jedi jedi = (Jedi) object;
return String.format("JEDI [name=%s, age=%s]", jedi.name, jedi.age);
}
return null;
}
@Override
public int getPriority() {
return 10;
}
}
Same as the Lotr library, StarWarsRepresentation is registered by creating a META-INF/services/org.assertj.core.presentation.Representation file that contain org.assertj.example.starwars.StarWarsRepresentation.
The consuming project specifies both libraries as dependencies, since both have registered a representation, AssertJ will discover them and keep them in a composite representation that aggregates all registered representaions.
The following test fails with frodo and luke being represented by LotrRepresentation and StarWarsRepresentation respectively.
Hobbit frodo = new Hobbit();
frodo.name = "Frodo";
frodo.age = "33";
Jedi luke = new Jedi();
luke.name = "Luke";
luke.age = "23";
assertThat(frodo).isEqualTo(luke);
Error message:
org.opentest4j.AssertionFailedError:
expected: JEDI [name=Luke, age=23]
but was: HOBBIT [name=Frodo, age=33]
2.5.8. Common assertions
This section describes the assertions common to all types, the Javadoc for common assertions methods is available here.
2.5.9. Object assertions
The Javadoc for Object assertions is available here.
2.5.10. String/CharSequence assertions
This section describes all the available assertions for CharSequence (including String, StringBuilder, StringBuffer, …):
The Javadoc for CharSequence assertions is available here.
2.5.11. Iterable and array assertions
Reference
All the available assertions are described in:
The next sections focus on some features worth knowing to get the best of AssertJ, notably:
-
Different ways of checking iterables/arrays content
-
Navigating to a given element to check it
-
Filtering elements before asserting]
-
Extracting/mapping elements before asserting
-
Comparing elements with a specific comparator
Checking iterables/arrays content
There are different flavors of contains assertion, here’s a table to help choose the most relevant one:
| Assertion | Description |
|---|---|
Verifies that the actual iterable/array contains the given values in any order |
|
Verifies that the actual group contains only the given values and nothing else in any order and ignoring duplicates (i.e. once a value is found, its duplicates are also considered found) |
|
Verifies that the actual iterable/array contains exactly the given values and nothing else in order |
|
Verifies that the actual iterable/array contains exactly the given values and nothing else in any order |
|
Verifies that the actual group contains the given sequence in the correct order and without extra values between the sequence values |
|
Verifies that the actual group contains the given subsequence in the correct order possibly with other values between them |
|
Verifies that the actual iterable/array contains the given values only once |
|
Verifies that the actual iterable/array contains at least one of the given values (like an |
the assertions above have a variant accepting an iterable/array argument, ex: containsExactly(E…) and containsExactlyElementsOf(Iterable)
|
Verify assertions on some elements
Satisfy
You can assert that all or any elements verify the given assertions with allSatisfy and anySatisfy, conversely noneSatisfy lets you assert that no elements verify the given assertions.
The given assertions are expressed with a Consumer (typically with a lambda).
Examples:
List<TolkienCharacter> hobbits = list(frodo, sam, pippin);
// all elements must satisfy the given assertions
assertThat(hobbits).allSatisfy(character -> {
assertThat(character.getRace()).isEqualTo(HOBBIT);
assertThat(character.getName()).isNotEqualTo("Sauron");
});
// at least one element must satisfy the given assertions
assertThat(hobbits).anySatisfy(character -> {
assertThat(character.getRace()).isEqualTo(HOBBIT);
assertThat(character.getName()).isEqualTo("Sam");
});
// no element must satisfy the given assertions
assertThat(hobbits).noneSatisfy(character -> assertThat(character.getRace()).isEqualTo(ELF));
if allSatisfy fails, all the elements and their failing the assertions are reported.
|
Match
You can assert that all or any elements match the given Predicate with allMatch and anyMatch, conversely noneMatch lets you assert that no elements verify the given predicate.
Examples:
List<TolkienCharacter> hobbits = list(frodo, sam, pippin);
assertThat(hobbits).allMatch(character -> character.getRace() == HOBBIT, "hobbits")
.anyMatch(character -> character.getName().contains("pp"))
.noneMatch(character -> character.getRace() == ORC);
| You can pass a predicate description to make the error message more explicit if the assertion fails. |
Navigating to a given element
The idea is to navigate to a given element in order to check it, you can navigate to the first, last or any element by index or if you expect only one element use singleElement.
| this is only available for iterables at the moment. |
First / last / element(index)
Use first, last and element(index) to navigate to the corresponding element, after navigating you can only use object assertions unless you have specified an Assert class or preferrably an InstanceOfAssertFactory as shown in the following examples.
Examples:
// only object assertions available after navigation
Iterable<TolkienCharacter> hobbits = list(frodo, sam, pippin);
assertThat(hobbits).first().isEqualTo(frodo);
assertThat(hobbits).element(1).isEqualTo(sam);
assertThat(hobbits).last().isEqualTo(pippin);
// strongly typed String assertions after navigation
Iterable<String> hobbitsName = list("frodo", "sam", "pippin");
// STRING is an InstanceOfAssertFactory from org.assertj.core.api.InstanceOfAssertFactories.STRING
// as() is just synthetic sugar for readability
assertThat(hobbitsName).first(as(STRING))
.startsWith("fro")
.endsWith("do");
assertThat(hobbitsName).element(1, as(STRING))
.startsWith("sa")
.endsWith("am");
assertThat(hobbitsName).last(as(STRING))
.startsWith("pip")
.endsWith("pin");
// alternative for strongly typed assertions
assertThat(hobbitsName, StringAssert.class).first()
.startsWith("fro")
.endsWith("do");
Single element
singleElement checks that the iterable has only one element and navigates to it, after navigating you can only use object assertions unless you have specified an Assert class or preferrably an InstanceOfAssertFactory as shown in the following examples.
Examples:
Iterable<String> babySimpsons = list("Maggie");
// only object assertions available
assertThat(babySimpsons).singleElement()
.isEqualTo("Maggie");
// to get specific typed assertions, pass the corresponding InstanceOfAssertFactory from
// org.assertj.core.api.InstanceOfAssertFactories.STRING), as() is just synthetic sugar for readability
assertThat(babySimpsons).singleElement(as(STRING))
.endsWith("gie");
// alternative for strongly typed assertions
assertThat(babySimpsons, StringAssert.class).singleElement()
.startsWith("Mag");
Filtering elements
Filtering is handy to target assertions on some specific elements, the filter criteria can be expressed by:
-
an element property/field having a specific value (or not) or in a set of values (or not)
-
an element matching some assertions
-
an element matching a Condition
Let’s explore these options in some examples taken from FilterExamples from the assertions-examples project.
Filtering with a Predicate
You specify the filter condition using simple predicate, best expressed with a lambda.
Example:
assertThat(fellowshipOfTheRing).filteredOn( character -> character.getName().contains("o") )
.containsOnly(aragorn, frodo, legolas, boromir);
Filtering on a property or a field
First you specify the property/field name to filter on and then its expected value. The filter first tries to get the value from a property, then from a field. Reading private fields is supported by default, but can be disabled globally by calling Assertions.setAllowExtractingPrivateFields(false).
Filter supports nested properties/fields. Note that if an intermediate value is null the whole nested property/field is considered to be null, for example reading "address.street.name" will return null if "address.street" is null.
Filters support these basic operations: not, in, notIn
import static org.assertj.core.api.Assertions.in;
import static org.assertj.core.api.Assertions.not;
import static org.assertj.core.api.Assertions.notIn;
...
// filters use introspection to get property/field values
assertThat(fellowshipOfTheRing).filteredOn("race", HOBBIT)
.containsOnly(sam, frodo, pippin, merry);
// nested properties are supported
assertThat(fellowshipOfTheRing).filteredOn("race.name", "Man")
.containsOnly(aragorn, boromir);
// you can apply different comparison
assertThat(fellowshipOfTheRing).filteredOn("race", notIn(HOBBIT, MAN))
.containsOnly(gandalf, gimli, legolas);
assertThat(fellowshipOfTheRing).filteredOn("race", in(MAIA, MAN))
.containsOnly(gandalf, boromir, aragorn);
assertThat(fellowshipOfTheRing).filteredOn("race", not(HOBBIT))
.containsOnly(gandalf, boromir, aragorn, gimli, legolas);
// you can chain multiple filter criteria
assertThat(fellowshipOfTheRing).filteredOn("race", MAN)
.filteredOn("name", not("Boromir"))
.containsOnly(aragorn);
Filtering on a function return value
This is a more flexible way of getting the value to filter on but note that there is no support for operators like not, in and notIn.
assertThat(fellowshipOfTheRing).filteredOn(TolkienCharacter::getRace, HOBBIT)
.containsOnly(sam, frodo, pippin, merry);
Filtering on null value
Filters the elements whose specified property/field is null.
Filter supports nested properties/fields. Note that if an intermediate value is null the whole nested property/field is considered to be null, for example reading "address.street.name" will return null if "address.street" is null.
TolkienCharacter pippin = new TolkienCharacter("Pippin", 28, HOBBIT);
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
TolkienCharacter merry = new TolkienCharacter("Merry", 36, HOBBIT);
TolkienCharacter mysteriousHobbit = new TolkienCharacter(null, 38, HOBBIT);
List<TolkienCharacter> hobbits = list(frodo, mysteriousHobbit, merry, pippin);
assertThat(hobbits).filteredOnNull("name"))
.singleElement()
.isEqualTo(mysteriousHobbit);
Filtering elements matching given assertions
Filters the iterable under test keeping only elements matching the given assertions specified with a Consumer.
Example: check hobbits whose age < 34
TolkienCharacter pippin = new TolkienCharacter("Pippin", 28, HOBBIT);
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
TolkienCharacter merry = new TolkienCharacter("Merry", 36, HOBBIT);
TolkienCharacter sam = new TolkienCharacter("Sam", 38, HOBBIT);
List<TolkienCharacter> hobbits = list(frodo, sam, merry, pippin);
assertThat(hobbits).filteredOnAssertions(hobbit -> assertThat(hobbit.age).isLessThan(34))
.containsOnly(frodo, pippin);
Filtering with a Condition
Filter the iterable/array under test keeping only elements matching the given Condition.
Two methods are available: being(Condition) and having(Condition). They do the same job - pick the one that makes your code more readable!
import org.assertj.core.api.Condition;
Condition<Player> mvpStats= new Condition<Player>(player -> {
return player.pointsPerGame() > 20 && (player.assistsPerGame() >= 8 || player.reboundsPerGame() >= 8);
}, "mvp");
List<Player> players;
players.add(rose); // Derrick Rose: 25 ppg - 8 assists - 5 rebounds
players.add(lebron); // Lebron James: 27 ppg - 6 assists - 9 rebounds
players.add(noah); // Joachim Noah: 8 ppg - 5 assists - 11 rebounds
// noah does not have more than 20 ppg
assertThat(players).filteredOn(mvpStats)
.containsOnly(rose, lebron);
Extracting elements values
What problem extracting solves
Let’s say you have called some service and got a list (or an array) of TolkienCharacter, to check the results you have to build the expected TolkienCharacters, that can be quite tedious!
List<TolkienCharacter> fellowshipOfTheRing = tolkienDao.findHeroes(); // frodo, sam, aragorn ...
// requires creation of frodo and aragorn, the expected TolkienCharacters
assertThat(fellowshipOfTheRing).contains(frodo, aragorn);
Instead, it is usually enough to check some fields or properties on the elements, for that you have to extract the fields/properties before performing your assertions, something like:
// extract the names ...
List<String> names = fellowshipOfTheRing.stream().map(TolkienCharacter::getName).collect(toList());
// ... and finally assert something
assertThat(names).contains("Boromir", "Gandalf", "Frodo", "Legolas");
This is too much work (even with the stream API), instead AssertJ can help extracting values from the elements under tests, there are several ways of doing so:
-
Extracting a single value per element
-
Extracting a multiple values per element
-
Extracting and flattening multiple values per element
Extracting single value per element
Specify the field/property to extract (or pass a Function) from each elements and perform assertions on the extracted values.
Extracting by name can access private fields/properties which is handy to check internals not exposed with public methods (lambda won’t work here), it also supports nested field/property like "race.name".
Examples:
// "name" needs to be either a property or a field of the TolkienCharacter class
assertThat(fellowshipOfTheRing).extracting("name")
.contains("Boromir", "Gandalf", "Frodo", "Legolas")
.doesNotContain("Sauron", "Elrond");
// specifying nested field/property is supported
assertThat(fellowshipOfTheRing).extracting("race.name")
.contains("Man", "Maia", "Hobbit", "Elf");
// same thing with a lambda which is type safe and refactoring friendly:
assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName)
.contains("Boromir", "Gandalf", "Frodo", "Legolas");
// same thing map an alias of extracting:
assertThat(fellowshipOfTheRing).map(TolkienCharacter::getName)
.contains("Boromir", "Gandalf", "Frodo", "Legolas");
Note that extracting one property can be made strongly typed by giving the property type as the second argument.
// to have type safe extracting, use the second parameter to pass the expected property type:
assertThat(fellowshipOfTheRing).extracting("name", String.class)
.contains("Boromir", "Gandalf", "Frodo", "Legolas")
.doesNotContain("Sauron", "Elrond");
Extracting multiple values
You can extract several values from the elements under test and check them using tuples.
As an example, let’s check the name, age and race’s name of each TolkienCharacter element:
// when checking several properties/fields you have to use tuples:
import static org.assertj.core.api.Assertions.tuple;
// extracting name, age and race.name nested property
assertThat(fellowshipOfTheRing).extracting("name", "age", "race.name")
.contains(tuple("Boromir", 37, "Man"),
tuple("Sam", 38, "Hobbit"),
tuple("Legolas", 1000, "Elf"));
// same assertion with functions for type safety:
assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName,
tolkienCharacter -> tolkienCharacter.age,
tolkienCharacter -> tolkienCharacter.getRace().getName())
.contains(tuple("Boromir", 37, "Man"),
tuple("Sam", 38, "Hobbit"),
tuple("Legolas", 1000, "Elf"));
The extracted name, age and race’s name values of the current element are grouped in a tuple, thus you need to use tuples for specifying the expected values.
More examples are available in IterableAssertionsExamples.java of the assertj-examples project.
Extracting and flattening multiple values per element
Flat extracting is hard to explain but easy to understand with an example, so let’s see how it works (in functional programming it is juts a flatMap).
Let’s assume we have a Player class with a teamMates property returning a List<Player> and we want to assert that it returns the expected players:
Player jordan = ... // initialized with Pippen and Kukoc team mates
Player magic = ... // initialized with Jabbar and Worthy team mates
List<Player> reallyGoodPlayers = list(jordan, magic);
// check all team mates by specifying the teamMates property (Player has a getTeamMates() method):
assertThat(reallyGoodPlayers).flatExtracting("teamMates")
.contains(pippen, kukoc, jabbar, worthy);
// alternatively, you can use a Function for type safety:
assertThat(reallyGoodPlayers).flatExtracting(BasketBallPlayer::getTeamMates)
.contains(pippen, kukoc, jabbar, worthy);
// flatMap is an alias of flatExtracting:
assertThat(reallyGoodPlayers).flatMap(BasketBallPlayer::getTeamMates)
.contains(pippen, kukoc, jabbar, worthy);
// if you use extracting instead of flatExtracting the result would be a list of list of players so the assertion becomes:
assertThat(reallyGoodPlayers).extracting("teamMates")
.contains(list(pippen, kukoc), list(jabbar, worthy));
You can use flatMap in place of flatExtracting (except for the variant taking a String)
|
Flat extracting can be used to group multiple values if you don’t want to use extracting and tuples:
// extract a list of values, flatten them and use contains assertion
assertThat(fellowshipOfTheRing).flatExtracting("name", "race.name")
.contains("Frodo", "Hobbit", "Legolas", "Elf");
// same assertions with Functions:
assertThat(fellowshipOfTheRing).flatExtracting(TolkienCharacter::getName,
tc -> tc.getRace().getName())
.contains("Frodo", "Hobbit", "Legolas", "Elf");
Comparing elements with a specific comparator
usingElementComparator allows you to change the way elements are compared (instead of using the elements equals method).
Examples:
List<TolkienCharacter> fellowshipOfTheRing = list(frodo, sam, merry, pippin, gandald, legolas, boromir, aragorn, gimli);
// the fellowshipOfTheRing includes Gandalf but not Sauron ...
assertThat(fellowshipOfTheRing).contains(gandalf)
.doesNotContain(sauron);
// ... but if we compare only races, Sauron is in fellowshipOfTheRing since he's a Maia like Gandalf
assertThat(fellowshipOfTheRing).usingElementComparator((t1, t2) -> t1.getRace().compareTo(t2.getRace()))
.contains(sauron);
2.5.12. Exception assertions
This chapter answers the question: how to assert that an exception has been thrown and check that it is the expected one?
Reference
All the available assertions are described in the AbstractThrowableAssert Javadoc.
In this chapter the term exception is used interchangeably with throwable.
The next sections focus on some features worth knowing to get the best of AssertJ, notably:
If you use java 7, check the Java 7 section.
Checking exception message
There are various ways for checking the exception message content, you can check the exact message, what it contains, its start, its end, if it matches a regex.
Most of the assertions expecting a String can use the String.format syntax.
Examples:
Throwable throwable = new IllegalArgumentException("wrong amount 123");
assertThat(throwableWithMessage).hasMessage("wrong amount 123")
.hasMessage("%s amount %d", "wrong", 123)
// check start
.hasMessageStartingWith("wrong")
.hasMessageStartingWith("%s a", "wrong")
// check content
.hasMessageContaining("wrong amount")
.hasMessageContaining("wrong %s", "amount")
.hasMessageContainingAll("wrong", "amount")
// check end
.hasMessageEndingWith("123")
.hasMessageEndingWith("amount %s", "123")
// check with regex
.hasMessageMatching("wrong amount .*")
// check does not contain
.hasMessageNotContaining("right")
.hasMessageNotContainingAny("right", "price")
Checking the cause and root cause
There are two approaches to check the cause and root cause, either directly or navigate to it with cause() and rootCause() and check it.
Checking the cause
You can check the cause directly if you know it, but that’s not always possible, and in such cases you can only check its type.
This is pretty limited in terms of assertions, a better approach is to navigate to the cause with cause() and take advantage of all existing exception assertions.
Direct cause assertions are limited …
NullPointerException cause = new NullPointerException("boom!");
Throwable throwable = new Throwable(cause);
assertThat(throwable).hasCause(cause)
// hasCauseInstanceOf will match inheritance.
.hasCauseInstanceOf(NullPointerException.class)
.hasCauseInstanceOf(RuntimeException.class)
// hasCauseExactlyInstanceOf will match only exact same type
.hasCauseExactlyInstanceOf(NullPointerException.class);
... but navigating to the cause allows taking advantage of all exception assertions:
// navigate before checking
assertThat(throwable).cause()
.hasMessage("boom!")
.hasMessage("%s!", "boom")
.hasMessageStartingWith("bo")
.hasMessageEndingWith("!")
.hasMessageContaining("boo")
.hasMessageContainingAll("bo", "oom", "!")
.hasMessageMatching("b...!")
.hasMessageNotContaining("bam")
.hasMessageNotContainingAny("bam", "bim")
// isInstanceOf will match inheritance.
.isInstanceOf(NullPointerException.class)
.isInstanceOf(RuntimeException.class)
// isExactlyInstanceOf will match only exact same type
.isExactlyInstanceOf(NullPointerException.class);
An alternative is using assertThatExceptionOfType combined with havingCause as shown in the following example:
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> { throw new RuntimeException(new IllegalArgumentException("boom!")); })
.havingCause()
.withMessage("boom!");
Checking the root cause
You can check the root cause directly with hasRootCause, hasRootCauseMessage and hasRootCauseInstanceOf if you have access to it but that’s not always possible, this is a bit limited
in terms of assertions, a better way is to navigate to the root cause with rootCause() and take advantage of all existing exception assertions.
Examples:
NullPointerException rootCause = new NullPointerException("null!");
Throwable throwable = new Throwable(new IllegalStateException(rootCause));
// direct root cause check
assertThat(throwable).hasRootCause(rootCause)
.hasRootCauseMessage("null!")
.hasRootCauseMessage("%s!", "null")
// hasRootCauseInstanceOf will match inheritance
.hasRootCauseInstanceOf(NullPointerException.class)
.hasRootCauseInstanceOf(RuntimeException.class)
// hasRootCauseExactlyInstanceOf will match only exact same type
.hasRootCauseExactlyInstanceOf(NullPointerException.class);
// navigate to root cause and check
assertThat(throwable).rootCause()
.hasMessage("null!")
.hasMessage("%s!", "null")
.hasMessageStartingWith("nu")
.hasMessageEndingWith("!")
.hasMessageContaining("ul")
.hasMessageContainingAll("nu", "ull", "l!")
.hasMessageMatching("n...!")
.hasMessageNotContaining("NULL")
.hasMessageNotContainingAny("Null", "NULL")
// isInstanceOf will match inheritance.
.isInstanceOf(NullPointerException.class)
.isInstanceOf(RuntimeException.class)
// isExactlyInstanceOf will match only exact same type
.isExactlyInstanceOf(NullPointerException.class);
An alternative is using assertThatExceptionOfType combined with havingRootCause as shown in the following example:
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> { throw new RuntimeException(new IllegalArgumentException(new NullPointerException("root error"))); })
.havingRootCause()
.withMessage("root error");
BDD style
BDD aficionados can separate WHEN and THEN steps by using catchThrowable(ThrowingCallable) to capture the Throwable and then perform assertions (ThrowingCallable is a functional interface which can be expressed as a lambda).
Example:
// GIVEN
String[] names = { "Pier ", "Pol", "Jak" };
// WHEN
Throwable thrown = catchThrowable(() -> System.out.println(names[9]));
// THEN
then(thrown).isInstanceOf(ArrayIndexOutOfBoundsException.class)
.hasMessageContaining("9");
// assertThat is also available but is less "BDD style"
assertThat(thrown).isInstanceOf(ArrayIndexOutOfBoundsException.class)
.hasMessageContaining("9");
catchThrowable returns null if no exception is thrown, but there is a better way to check that no exception is thrown.
|
catchThrowableOfType is a variation of catchThrowable where the caught exception type is verified and returned allowing to check the custom exception fields/properties.
Example:
class TextException extends Exception {
int line;
int column;
public TextException(String msg, int line, int column) {
super(msg);
this.line = line;
this.column = column;
}
}
TextException textException = catchThrowableOfType(TextException.class,
() -> { throw new TextException("boom!", 1, 5); });
// assertions succeed
assertThat(textException).hasMessageContaining("boom");
assertThat(textException.line).isEqualTo(1);
assertThat(textException.column).isEqualTo(5);
// fails as TextException is not a RuntimeException
catchThrowableOfType(RuntimeException.class, () -> { throw new TextException("boom!", 1, 5); });
Although the example above can be used for any exception type, enriched alternatives for catchThrowableOfType are also available to catch an instance of various commonly used exceptions:
-
catchException -
catchIllegalArgumentException -
catchIllegalStateException -
catchIndexOutOfBoundsException -
catchIOException -
catchNullPointerException -
catchReflectiveOperationException -
catchRuntimeException
For example, using catchIOException, the ThrowingCallable given as a parameter is executed: catchIOException returns null if no exception is thrown, otherwise it checks that the caught Throwable is of type IOException and casts it, making it convenient to perform subtype-specific assertions on it.
IOException iOException = catchIOException(() -> {throw new IOException("boom!");});
// assertions succeed
assertThat(iOException).hasMessage("boom!");
// succeeds as catchIOException returns null when the code does not throw any exceptions
assertThat(catchIOException(() -> {})).isNull();
// fails as the thrown instance is not an IOException
catchIOException(() -> {throw new Exception("boom!");});
The other catchXXX alternatives work the same way for their respective exception type.
assertThatThrownBy
assertThatThrownBy(ThrowingCallable) is an alternative to catchThrowable, use it if you find more readable.
Example:
assertThatThrownBy(() -> { throw new Exception("boom!"); }).isInstanceOf(Exception.class)
.hasMessageContaining("boom");
If the provided ThrowingCallable does not raise an exception, an assertion error is immediately thrown.
|
assertThatExceptionOfType
assertThatExceptionOfType is an alternative syntax that some people find more natural.
assertThatExceptionOfType(IOException.class).isThrownBy(() -> { throw new IOException("boom!"); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
If the provided ThrowingCallable does not raise an exception, an assertion error is immediately thrown.
|
Similarly to catchThrowableOfType, the latter syntax has been enriched for commonly used exceptions:
-
assertThatNullPointerException -
assertThatIllegalArgumentException -
assertThatIllegalStateException -
assertThatIOException
The previous example can be rewritten as:
assertThatIOException().isThrownBy(() -> { throw new IOException("boom!"); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
Testing that no exception is thrown
You can test that a piece of code does not throw any exception with:
// standard style
assertThatNoException().isThrownBy(() -> System.out.println("OK"));
// BDD style
thenNoException().isThrownBy(() -> System.out.println("OK"));
or similarly:
// standard style
assertThatCode(() -> System.out.println("OK")).doesNotThrowAnyException();
// BDD style
thenCode(() -> System.out.println("OK")).doesNotThrowAnyException();
With Java 7 (AssertJ 2.x)
Asserting on exceptions is not as nice compared to the Java 8 way, this is how you would do it in AssertJ 2.x :
-
Put the code that should throw the exception in a try-catch.
-
Call fail method immediately after, so that the test fails if the exception is not thrown.
-
Assert the caught exception.
Note that fail method can be statically imported from Assertions class.
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
// ... code omitted for brevity
assertThat(fellowshipOfTheRing).hasSize(9);
// here's the typical pattern to use Fail :
try {
fellowshipOfTheRing.get(9); // argggl !
// we should not arrive here => use fail to expresses that
// if IndexOutOfBoundsException was not thrown, test would fail the specified message
fail("IndexOutOfBoundsException expected because fellowshipOfTheRing has only 9 elements");
} catch (IndexOutOfBoundsException e) {
assertThat(e).hasMessage("Index: 9, Size: 9");
}
// Warning: don't catch Throwable as it would also catch the AssertionError thrown by fail method
// another way to do the same thing
try {
fellowshipOfTheRing.get(9); // argggl !
// if IndexOutOfBoundsException was not thrown, test would fail with message :
// "Expected IndexOutOfBoundsException to be thrown"
failBecauseExceptionWasNotThrown(IndexOutOfBoundsException.class);
} catch (IndexOutOfBoundsException e) {
assertThat(e).hasMessage("Index: 9, Size: 9");
}
2.5.13. Field by field recursive comparison
AssertJ Core provides a fluent recursive comparison API for Object assertions with the following capabilities:
The recursive comparison is meant to to replace isEqualToComparingFieldByFieldRecursively.
Basic usage
The recursive comparison mode starts after calling usingRecursiveComparison().
Here’s a simple example:
public class Person {
String name;
double height;
Home home = new Home();
}
public class Home {
Address address = new Address();
Date ownedSince;
}
public static class Address {
int number;
String street;
}
Person sherlock = new Person("Sherlock", 1.80);
sherlock.home.ownedSince = new Date(123);
sherlock.home.address.street = "Baker Street";
sherlock.home.address.number = 221;
Person sherlock2 = new Person("Sherlock", 1.80);
sherlock2.home.ownedSince = new Date(123);
sherlock2.home.address.street = "Baker Street";
sherlock2.home.address.number = 221;
// assertion succeeds as the data of both objects are the same.
assertThat(sherlock).usingRecursiveComparison()
.isEqualTo(sherlock2);
// assertion fails as Person equals only compares references.
assertThat(sherlock).isEqualTo(sherlock2);
The comparison is not symmetrical since it is limited to actual fields.
The algorithm gathers actual fields and then compares them to the corresponding expected fields. It is then possible for the expected object to have more fields than actual, which can be handy when comparing a base type to a subtype with additional fields.
How field values are resolved
The recursive comparison uses introspection to find out the fields to compare and their values.
It first looks for the object under test fields (skipping any ignored ones as specified in the configuration), then it looks for the same fields in the expected object to compare to.
The next step is resolving the field values using first a getter method (if any) or reading the field value. The getter methods for a field x are getX() or isX() for boolean fields.
If you enable bare properties resolution, a method x() is also used considered as a valid getter.
Bare name property is enabled by calling Assertions.setExtractBareNamePropertyMethods(true); (it is disabled by default since 3.18.0).
Lastly if the object under test is a map, the recursive comparison tries to resolve the field value by looking it up in the map with map.get(fieldName).
Since 3.24.0, you can specify your own strategy on how the recursive comparison resolve the values to compare, go to section specifying how to introspect the objects to compare for details.
Breaking changes
Since 3.18.0 bare name getter resolution are disabled by default, to get the previous behaviour back, call Assertions.setExtractBareNamePropertyMethods(true);
Since 3.17.0 it does not use anymore equals methods of classes that have overridden it, so no need to force recursive comparison on these classes. To get the previous behavior back, use usingOverriddenEquals().
isNotEqualTo
Since 3.17.0 isNotEqualTo is available in the recursive API, example:
// equals not overridden in TolkienCharacter
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
TolkienCharacter frodoClone = new TolkienCharacter("Frodo", 33, HOBBIT);
TolkienCharacter youngFrodo = new TolkienCharacter("Frodo", 22, HOBBIT);
// Pass as equals compares object references
assertThat(frodo).isNotEqualTo(frodoClone);
// Fail as frodo and frodoClone are equals when doing a field by field comparison.
assertThat(frodo).usingRecursiveComparison()
.isNotEqualTo(frodoClone);
// Pass as one the age fields differ between frodo and youngFrodo.
assertThat(frodo).usingRecursiveComparison()
.isNotEqualTo(youngFrodo);
Strict or lenient comparison
By default the objects to compare can be of different types but must have the same properties/fields. For example if object under test has a work field of type Address, the expected object to compare the object under test to must also have one but it can of a different type like AddressDto.
It is possible to enforce strict type checking by calling withStrictTypeChecking() and make the comparison fail whenever the compared objects or their fields are not compatible. Compatible means that the expected object/field types are the same or a subtype of actual/field types, for example if actual is an Animal and expected a Dog, they will be compared field by field in strict type checking mode.
public class Person {
String name;
double height;
Person bestFriend;
}
Person sherlock = new Person("Sherlock", 1.80);
sherlock.bestFriend = new Person("Watson", 1.70);
Person sherlockClone = new Person("Sherlock", 1.80);
sherlockClone.bestFriend = new Person("Watson", 1.70);
// assertion succeeds as sherlock and sherlockClone have the same data and types
assertThat(sherlock).usingRecursiveComparison()
.withStrictTypeChecking()
.isEqualTo(sherlockClone);
// Let's now define a data structure similar to Person
public class PersonDTO {
String name;
double height;
PersonDTO bestFriend;
}
PersonDTO sherlockDto = new PersonDTO("Sherlock", 1.80);
sherlockDto.bestFriend = new PersonDTO("Watson", 1.70);
// assertion fails as Person and PersonDTO are not compatible even though they have the same data
assertThat(sherlock).usingRecursiveComparison()
.withStrictTypeChecking()
.isEqualTo(sherlockDto);
// Let's define a subclass of Person
public class Detective extends Person {
boolean busy;
}
Detective detectiveSherlock = new Detective("Sherlock", 1.80);
detectiveSherlock.bestFriend = new Person("Watson", 1.70);
detectiveSherlock.busy = true;
// assertion succeeds as Detective inherits from Person and
// only Person's fields are included into the comparison.
assertThat(sherlock).usingRecursiveComparison()
.withStrictTypeChecking()
.isEqualTo(detectiveSherlock);
Ignoring fields in the comparison
It is possible to ignore fields of the object under test in the comparison, this is can be useful when a field has a generated value (like the current time) or is simply not relevant to compare.
There are a few ways to specify the fields to ignore:
-
directly with
ignoringFields(String… fieldsToIgnore) -
by regexes with
ignoringFieldsMatchingRegexes(String… regexes) -
by types with
ignoringFieldsOfTypes(Class… typesToIgnore)
Nested fields can be specified like this: home.address.street
It is also possible to ignore the object under test with ignoringActualNullFields().
Examples
Person sherlock = new Person("Sherlock", 1.80);
sherlock.home.address.street = "Baker Street";
sherlock.home.address.number = 221;
// strangely moriarty and sherlock have the same height!
Person moriarty = new Person("Moriarty", 1.80);
moriarty.home.address.street = "Crime Street";
moriarty.home.address.number = 221;
// assertion succeeds as name and home.address.street fields are ignored in the comparison
assertThat(sherlock).usingRecursiveComparison()
.ignoringFields("name", "home.address.street")
.isEqualTo(moriarty);
// assertion succeeds as once a field is ignored, its subfields are too
assertThat(sherlock).usingRecursiveComparison()
.ignoringFields("name", "home")
.isEqualTo(moriarty);
// ignoring fields matching regexes: name and home match .*me
assertThat(sherlock).usingRecursiveComparison()
.ignoringFieldsMatchingRegexes(".*me")
.isEqualTo(moriarty);
// ignoring null fields example:
sherlock.name = null;
sherlock.home.address.street = null;
assertThat(sherlock).usingRecursiveComparison()
.ignoringActualNullFields()
.isEqualTo(moriarty);
// ignore height and address fields by type:
Person tallSherlock = new Person("sherlock", 2.10);
tallSherlock.home.address.street = "Long Baker Street";
tallSherlock.home.address.number = 222;
assertThat(sherlock).usingRecursiveComparison()
.ignoringFieldsOfTypes(double.class, Address.class)
.isEqualTo(tallSherlock);
Using overridden equals
Since 3.17.0 the recursive comparison does not use overridden equals methods to compare fields anymore, it performs a recursive comparison on these fields, it is possible to change that behavior by calling usingOverriddenEquals().
Once using overridden equals methods is enabled, you can disable it for certain types or fields (and perform a recursive comparison instead) using the following methods:
-
ignoringOverriddenEqualsForTypes(Class…)Any fields of these classes are compared recursively -
ignoringOverriddenEqualsForFields(String…)Any given fields are compared recursively -
ignoringOverriddenEqualsForFieldsMatchingRegexes(String…)Any fields matching one of these regexes are compared recursively -
ignoringAllOverriddenEquals()except for java types, all fields are compared field by field recursively
Example:
public class Person {
String name;
double height;
Home home = new Home();
}
public class Home {
Address address = new Address();
}
public static class Address {
int number;
String street;
// only compares number, ouch!
@Override
public boolean equals(final Object other) {
if (!(other instanceof Address)) return false;
Address castOther = (Address) other;
return Objects.equals(number, castOther.number);
}
}
Person sherlock = new Person("Sherlock", 1.80);
sherlock.home.address.street = "Baker Street";
sherlock.home.address.number = 221;
Person sherlock2 = new Person("Sherlock", 1.80);
sherlock2.home.address.street = "Butcher Street";
sherlock2.home.address.number = 221;
// assertion succeeds but that's not what we expected since the home.address.street fields differ
// but the equals implementation in Address does not compare them.
assertThat(sherlock).usingRecursiveComparison()
.usingOverriddenEquals()
.isEqualTo(sherlock2);
// to avoid the previous issue, we force a recursive comparison on the Address type
// now this assertion fails as expected since the home.address.street fields differ.
assertThat(sherlock).usingRecursiveComparison()
.usingOverriddenEquals()
.ignoringOverriddenEqualsForTypes(Address.class)
.isEqualTo(sherlock2);
Ignoring all expected null fields
By using ignoringExpectedNullFields() the recursive comparison will exclude from the comparison any null fields in the expected object.
One use case for that is when the object under test have fields with values hard to predict (id, timestamp, …), with this feature you simply build the expected object with null values values for these fields and they won’t be compared.
Example:
public class Person {
String name;
double height;
Home home = new Home();
}
public class Home {
Address address = new Address();
}
public static class Address {
int number;
String street;
}
Person sherlock = new Person("Sherlock", 1.80);
sherlock.home.address.street = "Baker Street";
sherlock.home.address.number = 221;
Person noName = new Person(null, 1.80);
noName.home.address.street = null;
noName.home.address.number = 221;
// assertion succeeds as name and home.address.street fields are ignored in the comparison
assertThat(sherlock).usingRecursiveComparison()
.ignoringExpectedNullFields()
.isEqualTo(noName);
// assertion fails as name and home.address.street fields are populated for sherlock but not for noName.
assertThat(noName).usingRecursiveComparison()
.ignoringExpectedNullFields()
.isEqualTo(sherlock);
Ignoring all actual empty optional fields
ignoringActualEmptyOptionalFields() makes the recursive comparison to ignore all actual empty optional fields (including Optional, OptionalInt, OptionalLong and OptionalDouble).
Note that the expected object empty optional fields are not ignored, this only applies to actual’s fields.
public class Person {
String name;
OptionalInt age;
OptionalLong id;
OptionalDouble height;
Home home = new Home();
}
public class Home {
String address;
Optional<String> phone;
}
Person homerWithoutDetails = new Person("Homer Simpson");
homerWithoutDetails.home.address.street = "Evergreen Terrace";
homerWithoutDetails.home.address.number = 742;
homerWithoutDetails.home.phone = Optional.empty();
homerWithoutDetails.age = OptionalInt.empty();
homerWithoutDetails.id = OptionalLong.empty();
homerWithoutDetails.height = OptionalDouble.empty();
Person homerWithDetails = new Person("Homer Simpson");
homerWithDetails.home.address.street = "Evergreen Terrace";
homerWithDetails.home.address.number = 742;
homerWithDetails.home.phone = Optional.of("(939) 555-0113");
homerWithDetails.age = OptionalInt.of(39);
homerWithDetails.id = OptionalLong.of(123456);
homerWithDetails.height = OptionalDouble.of(1.83);
// assertion succeeds as phone is ignored in the comparison
assertThat(homerWithoutDetails).usingRecursiveComparison()
.ignoringActualEmptyOptionalFields()
.isEqualTo(homerWithDetails);
// assertion fails as phone, age, id and height are not ignored and are populated for homerWithDetails but not for homerWithoutDetails.
assertThat(homerWithDetails).usingRecursiveComparison()
.ignoringActualEmptyOptionalFields()
.isEqualTo(homerWithoutDetails);
Specifying how to compare specific types or fields in the comparison
You can specify how to compare values per (nested) fields or type with the methods below (but before calling isEqualTo otherwise this has no effect!):
-
withEqualsForFields(BiPredicate, String…)orwithComparatorForFields(Comparator, String…)for one or multiple fields -
withEqualsForType(BiPredicate, Class)orwithComparatorForType(Comparator, Class)for a given type
Note that comparisons specified for fields take precedence over the ones specified for types.
By default floats are compared with a precision of 1.0E-6 and doubles with 1.0E-15.
Prefer using withEqualsForFields/withEqualsForType, providing a BiPredicate is simpler than a Comparator (unless you have one already defined).
|
Examples:
public class TolkienCharacter {
String name;
double height;
}
TolkienCharacter frodo = new TolkienCharacter("Frodo", 1.2);
TolkienCharacter tallerFrodo = new TolkienCharacter("Frodo", 1.3);
TolkienCharacter reallyTallFrodo = new TolkienCharacter("Frodo", 1.9);
BiPredicate<Double, Double> closeEnough = (d1, d2) -> Math.abs(d1 - d2) <= 0.5;
// same comparison expressed with a Comparator:
// Comparator<Double> closeEnough = (d1, d2) -> Math.abs(d1 - d2) <= 0.5 ? 0 : 1;
// assertion succeeds
assertThat(frodo).usingRecursiveComparison()
.withEqualsForFields(closeEnough, "height")
.isEqualTo(tallerFrodo);
assertThat(frodo).usingRecursiveComparison()
.withEqualsForType(closeEnough, Double.class)
.isEqualTo(tallerFrodo);
// assertions fail
assertThat(frodo).usingRecursiveComparison()
.withEqualsForFields(closeEnough, "height")
.isEqualTo(reallyTallFrodo);
assertThat(frodo).usingRecursiveComparison()
.withEqualsForType(closeEnough, Double.class)
.isEqualTo(reallyTallFrodo);
Overriding error messages for specific fields or types
If AssertJ difference error description is not yo your liking, you can override it either by fields or types.
You can override messages for all fields of a given type, example for Double:
withErrorMessageForType("Double field differ", Double.class)
Alternatively can override messages for some specific fields which must be specified from the root object, for example if Foo has a Bar field and both have an id field, one can register a message for Foo and Bar id by calling:
withErrorMessageForFields("id values differ", "foo.id", "foo.bar.id")
Messages registered with withErrorMessageForFields have precedence over the ones registered with withErrorMessageForType.
Example overriding message for a field:
public class TolkienCharacter {
String name;
double height;
}
TolkienCharacter frodo = new TolkienCharacter("Frodo", 1.2);
TolkienCharacter tallerFrodo = new TolkienCharacter("Frodon", 1.4);
String message = "The field 'height' differ.";
// assertion fails
assertThat(frodo).usingRecursiveComparison()
.withErrorMessageForFields(message, "height")
.isEqualTo(tallerFrodo);
and the error will report the height field with the given overridden message instead of the one computed by AssertJ as with the name error:
Expecting actual:
TolkienCharacter [name=Frodo, height=1.2]
to be equal to:
TolkienCharacter [name=Frodon, height=1.4]
when recursively comparing field by field, but found the following 2 differences:
The field 'height' differ.
field/property 'name' differ:
- actual value : "Frodo"
- expected value: "Frodon"
The recursive comparison was performed with this configuration:
- no overridden equals methods were used in the comparison (except for java types)
- these types were compared with the following comparators:
- java.lang.Double -> DoubleComparator[precision=1.0E-15]
- java.lang.Float -> FloatComparator[precision=1.0E-6]
- java.nio.file.Path -> lexicographic comparator (Path natural order)
- actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior).
- these fields had overridden error messages:
- height
Example overriding message for a type:
String message = "Double field differ";
// assertion fails
assertThat(frodo).usingRecursiveComparison()
.withErrorMessageForType(message, Double.class)
.isEqualTo(tallerFrodo);
and the error will report the height field with the given overridden message instead of the one computed by AssertJ as with the name error:
Expecting actual:
TolkienCharacter [name=Frodo, height=1.2]
to be equal to:
TolkienCharacter [name=Frodon, height=1.4]
when recursively comparing field by field, but found the following 2 differences:
Double field differ.
field/property 'name' differ:
- actual value : "Frodo"
- expected value: "Frodon"
The recursive comparison was performed with this configuration:
- no overridden equals methods were used in the comparison (except for java types)
- these types were compared with the following comparators:
- java.lang.Double -> DoubleComparator[precision=1.0E-15]
- java.lang.Float -> FloatComparator[precision=1.0E-6]
- java.nio.file.Path -> lexicographic comparator (Path natural order)
- actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior).
- these types had overridden error messages:
- height
Recursive comparison for iterable assertions
usingRecursiveFieldByFieldElementComparator(RecursiveComparisonConfiguration) enables the recursive comparison for any iterable assertion as opposed to usingRecursiveComparison() which only allows isEqualTo and isNotEqualTo, the main difference between both isEqualTo assertions is that the usingRecursiveComparison one will give a detailed differences report while the usingRecursiveFieldByFieldElementComparator one will give a generic error message without details.
Another difference is that usingRecursiveComparison() exposes a fluent API to tweak the recursive comparison, to achieve the same you will need to initialize a RecursiveComparisonConfiguration and pass it to usingRecursiveFieldByFieldElementComparator, you can take advantage of the RecursiveComparisonConfiguration.builder() to do so.
Example:
public class Person {
String name;
boolean hasPhd;
}
public class Doctor {
String name;
boolean hasPhd;
}
Doctor drSheldon = new Doctor("Sheldon Cooper", true);
Doctor drLeonard = new Doctor("Leonard Hofstadter", true);
Doctor drRaj = new Doctor("Raj Koothrappali", true);
Person sheldon = new Person("Sheldon Cooper", false);
Person leonard = new Person("Leonard Hofstadter", false);
Person raj = new Person("Raj Koothrappali", false);
Person howard = new Person("Howard Wolowitz", false);
List<Doctor> doctors = list(drSheldon, drLeonard, drRaj);
List<Person> people = list(sheldon, leonard, raj);
RecursiveComparisonConfiguration configuration = RecursiveComparisonConfiguration.builder()
.withIgnoredFields("hasPhd")
.build();
// assertion succeeds as both lists contains equivalent items in order.
assertThat(doctors).usingRecursiveFieldByFieldElementComparator(configuration)
.contains(sheldon);
// assertion fails because leonard names are different.
leonard.setName("Leonard Ofstater");
assertThat(doctors).usingRecursiveFieldByFieldElementComparator(configuration)
.contains(leonard);
// assertion fails because howard is missing and leonard is not expected.
people = list(howard, sheldon, raj)
assertThat(doctors).usingRecursiveFieldByFieldElementComparator(configuration)
.contains(howard);
Specifying how to introspect the objects to compare
Since 3.24.0, you can specify your own strategy telling the recursive comparison how to resolve the values to compare, this is useful if the default strategy does not suit you.
To use your own introspection strategy, you need to:
-
implement
RecursiveComparisonIntrospectionStrategy -
call
withIntrospectionStrategy(myIntrospectionStrategy)with an instance of your strategy
AssertJ provides a few strategies out of the box:
-
ComparingFields: introspect fields only (no properties, map keys are not considered as fields) -
ComparingProperties: introspect properties only (no fields, map keys are not considered as properties) -
ComparingSnakeOrCamelCaseFields: compare fields only, can match camel case fields against snake case ones, ex:firstNamevsfirst_namewhich is useful when comparing types with different fields naming conventions -
ComparingNormalizedFields: an abstract strategy that compares fields after normalizing them, you just need to implementnormalizeFieldName(String fieldName)
ComparingSnakeOrCamelCaseFields is an example of ComparingNormalizedFields that normalizes snake case to camel case.
Here’s an example using ComparingSnakeOrCamelCaseFields where we compare Author/Book against AuthorDto/BookDto, Author/Book follow the regular camel case field naming convention while the dto classes follow the snake case naming convention.
The recursive comparison would fail comparing the Author/Book fields against AuthorDto/BookDto ones, it would not know to match Author.firstName against AuthorDto.first_name for example but with ComparingSnakeOrCamelCaseFields it will know how to match these fields.
Example:
Author martinFowler = new Author("Martin", "Fowler", 58, "https://www.thoughtworks.com/profiles/leaders/martin-fowler");
Book refactoring = new Book("Refactoring", martinFowler);
AuthorDto martinFowlerDto = new AuthorDto("Martin", "Fowler", 58, "https://www.thoughtworks.com/profiles/leaders/martin-fowler");
BookDto refactoringDto = new BookDto("Refactoring", martinFowlerDto);
RecursiveComparisonIntrospectionStrategy comparingSnakeOrCamelCaseFields = new ComparingSnakeOrCamelCaseFields();
// both assertions succeed
assertThat(refactoring).usingRecursiveComparison()
.withIntrospectionStrategy(comparingSnakeOrCamelCaseFields)
.isEqualTo(refactoringDto);
assertThat(refactoringDto).usingRecursiveComparison()
.withIntrospectionStrategy(comparingSnakeOrCamelCaseFields)
.isEqualTo(refactoring);
static class Author {
String firstName;
String lastName;
int age;
String profileURL;
Author(String firstName, String lastName, int age, String profileUrl) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.profileURL = profileUrl;
}
}
static class Book {
String title;
Author mainAuthor;
Book(String title, Author author) {
this.title = title;
this.mainAuthor = author;
}
}
static class AuthorDto {
String first_name;
String last_name;
int _age;
String profile_url;
AuthorDto(String firstName, String lastName, int age, String profileUrl) {
this.first_name = firstName;
this.last_name = lastName;
this._age = age;
this.profile_url = profileUrl;
}
}
static class BookDto {
String title;
AuthorDto main_author;
BookDto(String title, AuthorDto author) {
this.title = title;
this.main_author = author;
}
}
2.5.14. Recursive assertions
The recursive assertion allFieldsSatisfy lets you verify a Predicate is met for all the fields of the object under test graph recursively (but not the object itself).
For example if the object under test is an instance of class A, A has a B field and B a C field then allFieldsSatisfy checks A’s B field and B’s C field and all C’s fields.
Example:
class Author {
String name;
String email;
List<Book> books = new ArrayList<>();
Author(String name, String email) {
this.name = name;
this.email = email;
}
}
class Book {
String title;
Author[] authors;
Book(String title, Author[] authors) {
this.title = title;
this.authors = authors;
}
}
Author pramodSadalage = new Author("Pramod Sadalage", "p.sadalage@recursive.test");
Author martinFowler = new Author("Martin Fowler", "m.fowler@recursive.test");
Author kentBeck = new Author("Kent Beck", "k.beck@recursive.test");
Book noSqlDistilled = new Book("NoSql Distilled", new Author[] {pramodSadalage, martinFowler});
pramodSadalage.books.add(noSqlDistilled);
martinFowler.books.add(noSqlDistilled);
Book refactoring = new Book("Refactoring", new Author[] {martinFowler, kentBeck});
martinFowler.books.add(refactoring);
kentBeck.books.add(refactoring);
// assertion succeeds
assertThat(pramodSadalage).usingRecursiveAssertion()
.allFieldsSatisfy(field -> field != null);
The above example is best rewritten with hasNoNullFields() which is common enough that it is supported out of the box.
The recursive assertion provides these methods to exclude fields, the predicate won’t be applied on the excluded fields:
-
ignoringFields(String…fieldsToIgnore)- the assertion ignores the specified fields in the object under test -
ignoringFieldsMatchingRegexes(String…regexes)- the assertion ignores the fields matching the specified regexes in the object under test -
ignoringFieldsOfTypes(Class<?>…typesToIgnore)- the assertion ignores the object under test fields of the given types -
ignoringPrimitiveFields()- avoid running the assertion on primitive fields
2.5.15. Soft assertions
With soft assertions AssertJ collects all assertion errors instead of stopping at the first one.
| This is especially useful for long tests like end to end tests as we can fix all reported errors at once and avoid multiple failing runs. |
Since soft assertions don’t fail at the first error, you need to tell AssertJ when to report the captured assertion errors, there are different ways of doing so:
-
Calling
assertAll()(basic approach) -
Using a JUnit 4 rule that takes care of calling
assertAll()after each tests -
Using the provided JUnit 5 extension which injects a
SoftAssertionsor aBDDSoftAssertionsparameter and callsassertAll()after each tests -
Using a
AutoCloseableSoftAssertions
Soft assertions comes with a BDD flavor where assertThat is replaced by then.
If you have created your own custom Soft assertions it is possible to combine them all in a single soft assertions entry point.
Let’s see first how to use soft assertions requiring an explicit call to assertAll(), the other approaches that don’t require this explicitit call are described in the subsequent sections.
Example:
@Test
void basic_soft_assertions_example() {
SoftAssertions softly = new SoftAssertions(); (1)
softly.assertThat("George Martin").as("great authors").isEqualTo("JRR Tolkien"); (2)
softly.assertThat(42).as("response to Everything").isGreaterThan(100); (2)
softly.assertThat("Gandalf").isEqualTo("Sauron"); (2)
// Don't forget to call assertAll() otherwise no assertion errors are reported!
softly.assertAll(); (3)
}
| 1 | Build a SoftAssertions instance to record all assertion errors |
| 2 | Use softly.assertThat instead of the usual assertThat methods |
| 3 | Don’t forget to call assertAll() to report all assertion errors! |
The previous test fails with the message below reporting all the errors:
Multiple Failures (3 failures)
-- failure 1 --
[great authors]
Expecting:
<"George Martin">
to be equal to:
<"JRR Tolkien">
but was not.
-- failure 2 --
[response to Everything]
Expecting:
<42>
to be greater than:
<100>
-- failure 3 --
Expecting:
<"gandalf">
to be equal to:
<"sauron">
but was not.
BDD Soft assertions
BDD aficionados can use BDD soft assertions where assertThat is replaced by then.
Example:
@Test
void basic_bdd_soft_assertions_example() {
BDDSoftAssertions softly = new BDDSoftAssertions();
softly.then("George Martin").as("great authors").isEqualTo("JRR Tolkien");
softly.then(42).as("response to Everything").isGreaterThan(100);
softly.then("Gandalf").isEqualTo("Sauron");
// Don't forget to call assertAll() otherwise no assertion errors are reported!
softly.assertAll();
}
There are BDD soft assertions versions for the different soft assertions approaches:
-
AutoCloseableBDDSoftAssertions -
Using
JUnitBDDSoftAssertionsthat takes care of callingassertAll()after each tests -
Using a JUnit 5 extension that takes care of calling
assertAll()after each tests
JUnit 4 Soft assertions rule
The JUnit rule provided by AssertJ takes care of calling assertAll() at the end of each tests.
Example:
@Rule
public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
@Test
void junit4_soft_assertions_example() {
softly.assertThat("George Martin").as("great authors").isEqualTo("JRR Tolkien"); (2)
softly.assertThat(42).as("response to Everything").isGreaterThan(100); (2)
softly.assertThat("Gandalf").isEqualTo("Sauron"); (2)
// No need to call softly.assertAll(), this is automatically done by the JUnitSoftAssertions rule
}
In a similar way you can use JUnitBDDSoftAssertions where assertThat is replaced by then:
@Rule
public final JUnitBDDSoftAssertions softly = new JUnitBDDSoftAssertions();
@Test
void junit4_bdd_soft_assertions_example() {
softly.then("George Martin").as("great authors").isEqualTo("JRR Tolkien");
softly.then(42).as("response to Everything").isGreaterThan(100);
softly.then("Gandalf").isEqualTo("Dauron");
// No need to call softly.assertAll(), this is automatically done by the JUnitSoftAssertions rule
}
JUnit 5 soft assertions extension
SoftAssertionsExtension is a JUnit 5 extension that:
-
takes care of calling
assertAll()at the end of each tests -
supports initializing
SoftAssertionsProviderfield annotated with@InjectSoftAssertions -
supports injecting a
SoftAssertionsProviderparameter in each test methods
SoftAssertionsProvider is the interface that any concrete soft assertions class must implement, AssertJ provides two of them: SoftAssertions and BDDSoftAssertions, but custom implementations are also supported as long as they have a default constructor. See the end of combining soft assertions entry points section for an example.
JUnitJupiterSoftAssertions, JUnitJupiterBDDSoftAssertions and SoftlyExtension are now deprecated in favor of SoftAssertionsExtension.
|
SoftAssertionsProvider field injection
SoftAssertionsExtension supports injecting any instance of SoftAssertionsProvider into a class test field annotated with @InjectSoftAssertions.
The injection occurs before each test method execution, after each test assertAll() is invoked to verify that no soft assertions failed.
A nested test class can provide a SoftAssertionsProvider field when it extends this extension or can inherit the parent’s one.
You can have multiple soft assertion providers injected into a single test class. Assertions made on any of them will be collected in a single error collector and reported all together, in the same order that they failed.
This extension throws an ExtensionConfigurationException if:
-
the field is static or final or cannot be accessed;
-
the field type is not a concrete implementation of
SoftAssertionsProvider(or subclass); or -
the field type has no default constructor.
Example:
import org.assertj.core.api.SoftAssertions;
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(SoftAssertionsExtension.class)
public class JUnit5SoftAssertionsExtensionAssertionsExamples {
@InjectSoftAssertions
private SoftAssertions softly;
@Test
public void chained_soft_assertions_example() {
String name = "Michael Jordan - Bulls";
softly.assertThat(name).startsWith("Mi")
.contains("Bulls");
// no need to call softly.assertAll(), this is done by the extension
}
// nested classes test work too
@Nested
class NestedExample {
@Test
public void football_assertions_example() {
String kylian = "Kylian Mbappé";
softly.assertThat(kylian).startsWith("Ky")
.contains("bap");
// no need to call softly.assertAll(), this is done by the extension
}
}
}
SoftAssertionsProvider parameter injection
SoftAssertionsExtension supports injecting any SoftAssertionsProvider implementation as a parameter in any test method.
The term "test method" refers to any method annotated with @Test, @RepeatedTest, @ParameterizedTest, @TestFactory or @TestTemplate.
Notably, the extension is compatible with parameterized tests, the parameterized arguments must come first and the soft assertions argument last.
The scope of the SoftAssertionsProvider instance managed by this extension begins when a parameter of type SoftAssertionsProvider is resolved for a test method.
It ends after the test method has been executed, this is when assertAll() will be invoked on the instance to verify that no soft assertions failed.
Parameter injection and field injection can be mixed. Assertions made on the field- and parameter-injected soft assertion providers will all be collected and reported together when the extension calls assertAll().
This extension throws a ParameterResolutionException if the resolved SoftAssertionsProvider :