Extending assertions

AssertJ can be extends by Condition or writing your own assertions class.

Conditions

Assertions can be extended by using conditions, to create a condition you just have to implement org.assertj.assertions.core.Condition and its unique method matches.

Once your Condition is created, you can use it with methods: is(myCondition) or has(myCondition), both verifying that the condition is met (hint: pick the one that makes your code more readable).

Example:

// jedi is a Condition
assertThat("Yoda").is(jedi);

Conditions can be combined with :

  • not(Condition): given condition must not be met

  • allOf(Condition…​): all given conditions must be met

  • anyOf(Condition…​): one of the given conditions must be met

You can verify that a Condition is met on elements of a collection, with the following methods:

  • are(condition)/have(condition): all elements must meet the given condition

  • areAtLeast(n, condition)/haveAtLeast(n, condition): at least n elements must meet the given condition

  • areAtMost(n, condition)/haveAtMost(n, condition): no more than n elements must meet the given condition

  • areExactly(n, condition)/haveExactly(n, condition): exactly n elements must meet the given condition

Moreover all Condition related methods have their negation counterpart, is/isNot, have/doesNotHave, are/areNot, …​

The examples below are from assertj-examples and more specifically UsingConditionExamples.java.

Creating a Condition

Let’s define two similar conditions: jedi and jediPower with the same implementation to show that code readability is better when using jedi with is and jediPower with has.

import static org.assertj.core.util.Sets.newLinkedHashSet;

static Set<String> JEDIS = newLinkedHashSet("Luke", "Yoda", "Obiwan");

// implementation with Java 8 lambda
Condition<String> jediPower = new Condition<>(JEDIS::contains, "jedi power");

// implementation with Java 7
Condition<String> jedi = new Condition<String>("jedi") {
  @Override
  public boolean matches(String value) {
    return JEDIS.contains(value);
  }
};

The String parameter is describing the condition, this is used in error messages.

Using Conditions

Usage with single instances:

assertThat("Yoda").is(jedi);
assertThat("Vader").isNot(jedi);

assertThat("Yoda").has(jediPower);
assertThat("Solo").doesNotHave(jediPower);

The Condition description is used in error messages, ex:

assertThat("Vader").is(jedi);

will fail with the following error message:

"expecting:<'Vader'> to be:<jedi>"

Usage with collections:

assertThat(list("Luke", "Yoda")).are(jedi);
assertThat(list("Leia", "Solo")).areNot(jedi);

assertThat(list("Luke", "Yoda")).have(jediPower);
assertThat(list("Leia", "Solo")).doNotHave(jediPower);

assertThat(list("Luke", "Yoda", "Leia")).areAtLeast(2, jedi);
assertThat(list("Luke", "Yoda", "Leia")).haveAtLeast(2, jediPower);

assertThat(list("Luke", "Yoda", "Leia")).areAtMost(2, jedi);
assertThat(list("Luke", "Yoda", "Leia")).haveAtMost(2, jediPower);

assertThat(list("Luke", "Yoda", "Leia")).areExactly(2, jedi);
assertThat(list("Luke", "Yoda", "Leia")).haveExactly(2, jediPower);

Combining Conditions

Conditions can be combined with allOf(Condition…​) (logical and) or anyOf(Condition…​) (logical or), not can be used to invert one.

Let’s define a sith condition:

List<String> SITHS = list("Sidious", "Vader", "Plagueis");
Condition<String> sith = new Condition<>(SITHS::contains, "sith");

We can write these assertions:

assertThat("Vader").is(anyOf(jedi, sith));
assertThat("Solo").is(allOf(not(jedi), not(sith)));

Custom Assertions

Creating assertions specific to your own classes is interesting because these assertions will reflect the domain model. It’s a way to use Domain Driven Design ubiquitous language in your tests.

Writing your own assertions is simple: create a class inheriting from AbstractAssert and add your custom assertions methods.

Add a static method assertThat to provide a handy entry point to your new assertion class.

Sections:

  • Creating your own assertion class

  • Providing an entry point for all your custom assertions

  • Providing an entry point for all your custom assertions and AssertJ ones ?

  • Enabling soft assertions on your custom assertions

Creating your own assertion class

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; // Race is an enum
  private int age;
}

Let’s name our assertion class TolkienCharacterAssert, 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, satisfies, …​

public class TolkienCharacterAssert extends AbstractAssert<TolkienCharacterAssert, TolkienCharacter> { (1)

  public TolkienCharacterAssert(TolkienCharacter actual) { (2)
    super(actual, TolkienCharacterAssert.class);
  }

  public static TolkienCharacterAssert assertThat(TolkienCharacter actual) { (3)
    return new TolkienCharacterAssert(actual);
  }

  public TolkienCharacterAssert hasName(String name) { (4)
    // check that actual TolkienCharacter we want to make assertions on is not null.
    isNotNull();
    // check assertion logic
    if (!Objects.equals(actual.getName(), name)) {
      failWithMessage("Expected character's name to be <%s> but was <%s>", name, actual.getName());
    }
    // return this to allow chaining other assertion methods
    return this;
  }

  public TolkienCharacterAssert hasAge(int age) { (4)
    // check that actual TolkienCharacter we want to make assertions on is not null.
    isNotNull();
    // check assertion logic
    if (actual.getAge() != age) {
      failWithMessage("Expected character's age to be <%s> but was <%s>", age, actual.getAge());
    }
    // return this to allow chaining other assertion methods
    return this;
  }
}
1 Inherits from AbstractAssert
2 Constructor to build your assertion class with the object under test
3 An entry point to your specific assertion class to use with static import
4 assertions specific to TolkienCharacter

Using our custom assertion class

To use our custom assertion class, simply call the assertThat factory method with the object to test:

// 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 not too difficult, 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 going to do in the next section.

Providing an entry point for all custom assertions

Now that you have a bunch of custom assertions classes, you want to access them easily. Just create a CustomAssertions class providing static assertThat methods for each of your assertions classes.

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:

import static my.project.MyProjectAssertions.assertThat;
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");
}
You could also make your custom Assertions entry point class inherit AssertJ’s Assertions, that will work fine if and only if you have one entry point class for your custom assertions classes!

The problem with several entry point classes inheriting from AssertJ Assertions, then when you use them Java won’t be able to resolve which assertThat(String) method to use. The following code illustrates the issue:

// 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 class both MyAssertions and MyOtherAssertions inherits from)
  assertThat("frodo").contains("do");
}