Having the possibility to create assertions specific to your own classes is important because it makes your test assertions reflect the domain model. It's a way to use Domain Driven Design ubiquitous language in your tests. Custom assertions can be seen as a DSL of domain assertions.
Writing your own assertions is simple : create a class inheriting from AbstractAssert and add your custom assertions methods.
To be easy to use, you should also add a static method assertThat to provide an handy entry point to your new assertion class.
If you have a lot of classes you want to have assertions for, consider using AssertJ assertions generator.
Let's see how to do that with an example !
The example is taken from assertj-examples and more specifically TolkienCharacterAssert.java.
We want to have assertion for the TolkienCharacter domain model class shown below :
// getter/setter omitted for brevity
public class TolkienCharacter {
private String name;
private Race race;
private int age;
}
Let's name our assertion class TolkienCharacterAssert. To follow AssertJ conventions, we make it inherit from AbstractAssert and specify two generic parameters, the first is the class itself (needed for assertion chaining) and the second is the class we want to make assertions on : TolkienCharacter.
Inheriting from AbstractAssert will give you all the basic assertions : isEqualTo, isNull, ...
// javadoc omitted for brevity
// 1 - inherits from AbstractAssert !
public class TolkienCharacterAssert extends AbstractAssert<TolkienCharacterAssert, TolkienCharacter> {
// 2 - Write a constructor to build your assertion class with the object you want make assertions on.
public TolkienCharacterAssert(TolkienCharacter actual) {
super(actual, TolkienCharacterAssert.class);
}
// 3 - A fluent entry point to your specific assertion class, use it with static import.
public static TolkienCharacterAssert assertThat(TolkienCharacter actual) {
return new TolkienCharacterAssert(actual);
}
// 4 - a specific assertion !
public TolkienCharacterAssert hasName(String name) {
// check that actual TolkienCharacter we want to make assertions on is not null.
isNotNull();
// check condition
if (!Objects.equals(actual.getName(), name)) {
failWithMessage("Expected character's name to be <%s> but was <%s>", name, actual.getName());
}
// return the current assertion for method chaining
return this;
}
// 4 - another specific assertion !
public TolkienCharacterAssert hasAge(int age) {
// check that actual TolkienCharacter we want to make assertions on is not null.
isNotNull();
// check condition
if (actual.getAge() != age) {
failWithMessage("Expected character's age to be <%s> but was <%s>", age, actual.getAge());
}
// return the current assertion for method chaining
return this;
}
}
TolkienCharacter assertions usage :
// use assertThat from TolkienCharacterAssert to check TolkienCharacter
TolkienCharacterAssert.assertThat(frodo).hasName("Frodo");
// code is more elegant when TolkienCharacterAssert.assertThat is imported statically :
assertThat(frodo).hasName("Frodo");
Well, that was nice, but having to add a static import for each assertThat method of you custom assertion classes is not very handy, it would be better to have a unique assertion entry point. This is what we are goint to see in the next section.
Now that you have a bunch of custom assertions classes, you want to access them easily. Just create an Assertions class providing static assertThat methods for each of your assertions classes. For example:
public class MyProjectAssertions {
// give access to TolkienCharacter assertion
public static TolkienCharacterAssert assertThat(TolkienCharacter actual) {
return new TolkienCharacterAssert(actual);
}
// give access to TolkienCharacter Race assertion
public static RaceAssert assertThat(Race actual) {
return new RaceAssert(actual);
}
}
Usage:
// static import to use your custom assertions
import static my.project.MyProjectAssertions.assertThat;
// static import to use AssertJ core assertions
import static org.assertj.core.api.Assertions.assertThat;
...
@Test
public void successful_custom_assertion_example() {
// assertThat(TolkienCharacter) comes from my.project.MyProjectAssertions.assertThat
assertThat(frodo).hasName("Frodo");
// assertThat(String) comes from org.assertj.core.api.Assertions.assertThat
assertThat("frodo").contains("do");
}
Note that if you use AssertJ assertions generator, this class will be generated for you.
This is possible if you only have one entry point class for your custom assertions classes !
Let's say it's the case, and your entry point class is MyProjectAssertions, make it inherit from org.assertj.core.api.Assertions so that when you import statically MyProjectAssertions.assertThat you will be able to access all your custom assertions and Assert Core ones !
import org.assertj.core.api.Assertions;
public class MyProjectAssertions extends Assertions {
// same code as before
}
Usage:
// static import to use your custom assertions AND AssertJ core assertions
import static my.project.MyProjectAssertions.assertThat;
...
@Test
public void successful_custom_assertion_example() {
// assertThat(TolkienCharacter) comes from my.project.MyProjectAssertions.assertThat
assertThat(frodo).hasName("Frodo");
// assertThat(String) also comes from my.project.MyProjectAssertions.assertThat
// no need to import statically org.assertj.core.api.Assertions.assertThat;
assertThat("frodo").contains("do");
}
So you have two entry point classes MyAssertions and MyOtherAssertions both inheriting from org.assertj.core.api.Assertions. You use them in a test like in the code below. Problem ! Java can't choose which assertThat(String) method to use, the one from MyAssertions or the one MyOtherAssertions ?
// both MyAssertions and MyOtherAssertions inherit from org.assertj.core.api.Assertions
import static my.project.MyAssertions.assertThat;
import static my.project.MyOtherAssertions.assertThat;
...
@Test
public void ambiguous_assertThat_resolution() {
// ERROR : assertThat(String) is ambiguous !
// assertThat(String) is available from MyAssertions AND MyOtherAssertions
// (it is defined in Assertions the base class of both MyAssertions and MyOtherAssertions)
assertThat("frodo").contains("do");
}
The solution is simple, don't make your classes inherit from org.assertj.core.api.Assertions and use import static org.assertj.core.api.Assertions.assertThat to access core assertions.
You may want to perform soft assertions using your custom assertions. However, the provided SoftAssertions class does not know about your custom classes. For example:
SoftAssertions softly = new SoftAssertions();
softly.assertThat(frodo).hasName("frodo");
// The previous line does not compile, as softly.assertThat(frodo) returns an ObjectAssert
To enable soft assertions for your custom assertion classes, you will need to implement a class that extends SoftAssertions, and include an assertThat method for every class that you want to be able to softly assert on.
The class should look like this:
// Javadocs and imports omitted for brevity
public class MyProjectSoftAssertions extends SoftAssertions {
public TolkienCharacterAssert assertThat(TolkienCharacter actual) {
return proxy(TolkienCharacterAssert.class, TolkienCharacter.class, actual);
}
// additional custom assertions as desired.
}
Note that each assertThat method must return an assertion object generated by a call the proxy method. The arguments for the call to proxy are always the class of the custom assertion object being returned, the class of the object being asserted on, and the object being asserted on itself.
Extending SoftAssertions allows you to softly assert on the classes your custom assertions target, as well as on everything you can softly assert on using SoftAssertions itself. Returning to our non-compiling code from above, now we can do the following:
MyProjectSoftAssertions softly = new MyProjectSoftAssertions();
softly.assertThat(frodo).hasName("frodo"); // custom assertion
softly.assertThat("ABC").endsWith("C"); // standard assertion
softly.assertAll();
If you have a lot of classes and want custom assertions for all of them, it can be a lot of work !
In that case, you can generate custom assertions with AssertJ assertions generator which comes with a maven plugin. It takes classes or packages as input and generates the corresponding specific assertions classes (for all classes of the specified packages and subpackages).
The assertions generator looks for properties to know what to generate. For example, if you have a Person class with an address property, it will generate a hasAddress assertion as part of a PersonAssert assertion class.
Once assertions classes are generated, you can put them in source control and enrich them with new assertions.
AssertJ assertions generator will also generate entry point classes for your custom assertions !