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 ?
C’est peut-être un problème de conception ? ↩︎