AssertJ est enfin sorti avec le support de Java8.

Je le considère comme le meilleur outil de tests unitaires pour Java. Son approche est la suivante : une assertion doit être lisible par un humain.

Pour vous en faire la démonstration, je vais vous montrer plusieurs exemples avec assertJ, en les comparant avec les assertions Junit.

Exemples

Boolean check

Junit

assertTrue(myBooleanValue);

AssertJ

assertThat(myBooleanValue).isTrue();

List checks

Supposons cette liste :

ImmutableList<String> elementList = ImmutableList.of(
						"element1", 
						"element2", 
						"element3");

Si je veux vérifier que les éléments sont bien présents

Junit

assertEquals(3, elementList.size());
assertEquals("element1", elementList.get(0));
assertEquals("element2", elementList.get(1));
assertEquals("element3", elementList.get(2));

AssertJ

assertThat(elementList).hasSize(3);
assertThat(elementList).containsExactly("element1", "element2", "element3");

Et je ne vous montre que le containsExactly, il en existe plein d’autres (containsOnly, containsSubsequence, …).

DRY & KISS

C’est vraiment ce que j’apprécie dans cette librairie : tout a été pensé pour que l’on ne perde pas de temps dans nos asserts.

Et quand on se retrouve avec des cas tordus1, il y’a la possibilité de définir vos propres assertions (il y’a même un générateur pour ça).

Et Java 8 dans tout ça ?

La version 3.0.0 d’AssertJ fonctionne avec Java 8 et apporte son lot de sucre syntaxique.

Vous pouvez jeter un oeil sur la release note.

Date/Time assertions

Regardez les exemples de la release note, il n’y a rien à rajouter, c’est parfait, tout est là.

Exceptions

Junit

Avec Junit, plusieurs choix, soit :

  • on catch et on assert (Berk)
  • on marque dans l’annotation @Test que l’on attend une exception (Re-Berk, on ne sait pas si l’exception a été lancée par ce qu’on voulait, ou par du code au dessus)
  • on utilise la Rule ExpectedException
@Rule
public ExpectedException expectedException = ExpectedException.none();
...

@Test
public void shouldThrowExceptionWhenNull() throws Exception {
    expectedException.expect(NullPointerException.class);
    expectedException.expectMessage("Enum is null");
    MyEnum.fromName(null);
}

AssertJ 3

@Test
public void shouldThrowExceptionWhenNull() throws Exception {
    Throwable throwable = catchThrowable(() -> MyEnum.fromName(null));

    assertThat(throwable).isInstanceOf(NullPointerException.class)
                         .hasMessage("Enum is null");
}

Maintenant, on est capable de séparer le “When” du “Then”, chose qui n’était avant possible qu’en faisant un try-catch.

Optionals

Junit

@Test
public void shouldAssertOptional() throws Exception {
    Optional<String> optional = Optional.of("optionalTest");
    assertTrue(optional.isPresent());
    assertEquals("optionalTest", optional.get());
}

AssertJ 3

@Test
public void shouldAssertOptional() throws Exception {
    Optional<String> optional = Optional.of("optionalTest");
    assertThat(optional).isPresent()
                        .isEqualTo("optionalTest");
}

Approximation des primitives

Je ne suis pas sûr de l’utilité de celle là, d’un côté je pense que si on n’est pas capable de déterminer la sortie exacte d’un test, c’est qu’on a un problème. De l’autre, je me dis que ça peut être super, par exemple quand on fait des tests avec des nombres pseudo-aléatoires.

Bref, ça donne ça :

assertThat(30).isCloseTo(40, within(10));

matches

Celui là peut être super, ou super dangereux.

Voici l’exemple de la release note:

TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
// actual refers to frodo 
assertThat(frodo).matches(actual -> actual.age > 30 && actual.getRace() == HOBBIT);

Si on change l’âge de frodo à ‘25’ l’erreur remontée est la suivante :

java.lang.AssertionError: 
Expecting:
  <eu.bitard.AssertJ3Test$TolkienCharacter@721e0f4f> to match given predicate.

You can use 'matches(Predicate p, String description)' to have a better error message
For example:
  assertThat(player).matches(p -> p.isRookie(), "is rookie");
will give an error message looking like:

Expecting:
  <player>
to match 'is rookie' predicate
	at eu.bitard.AssertJ3Test.shouldAssertMatches(AssertJ3Test.java:28)
	.....

Je sais pas vous, mais moi je préfère ça :

ava.lang.AssertionError: 
Expecting:
 <25>
to be greater than:
 <30> 
	at eu.bitard.AssertJ3Test.shouldAssertMatches(AssertJ3Test.java:28)
	...

Ce qui est donné par les asserts suivant :

assertThat(frodo.age).isGreaterThan(30);
assertThat(frodo.race).isEqualTo(Race.HOBBIT);

Bilan

C’est une super bonne nouvelle, surtout pour les dates, les exceptions et les optionals. Pour les autres, je suis plus mitigé, à voir à l’usage.

Et vous, vous en pensez quoi ?

  1. C’est peut-être un problème de conception ?