Le mécanisme d’extension de JUnit 5 est plus simple et plus pratique que ceux de JUnit 4.
Il remplace à la fois @RunWith
, @Rule
et @ClassRule
.
Utiliser une extension
JUnit a deux annotations pour appliquer une extension sur une classe de test, @ExtendWith
et @RegisterExtension
.
@ExtendWith
s’utilise habituellement sur la classe de test, comme @RunWith
de JUnit 4.
La première différence, c’est qu’en JUnit 5, il est possible d’utiliser plusieurs extensions, en passant un tableau à l’annotation ou en l’utilisant plusieurs fois (elle est repeatable).
La deuxième différence, c’est qu’elle peut s’utiliser sur un méthode de test, un champ ou un paramètre de méthode.
@ExtendWith(LoggingExtension.class)
public class SomeTest {
//...
}
En remplacement de @ExtendWith
, on peut aussi utiliser une annotation dédiée, elle-même annotée par @ExtendWith
.
Cette annotation peut être fournie avec l’extension ou faite maison.
@Inherited
@Retention(RUNTIME)
@ExtendWith(LoggingExtension.class)
public @interface Logging {
}
@Logging
public class SomeTest {
//...
}
@RegisterExtension
s’utilise uniquement sur un champ, comme @Rule
ou @ClassRule
de JUnit 4.
Sa valeur ajoutée par rapport à @ExtendWith
, c’est qu’on instancie nous-même l’extension, avec la possibilité de choisir des paramètres d’initialisation.
public class SomeTest {
@RegisterExtension
LoggingExtension loggingExtension = LoggingExtension.named("@RegisterExtension");
//...
}
Extensions par défaut
Les extensions ci-dessous sont activées systématiquement. Elles permettent d’utiliser des annotations ou d’injecter des paramètres aux méthodes de tests ou du cycle de vie.
-
DisabledCondition : désactive les tests annotés avec
@Disabled
-
TempDirectory : création de répertoire temporaire avec
@TempDir
-
TimeoutExtension : applique un délai d’exécution aux tests annotés avec
@Timeout
-
RepeatedTestExtension : exécute de façon répétée les tests annotés avec
@RepeatedTest
-
TestInfoParameterResolver : injecte les paramètres de tests de type
TestInfo
-
TestReporterParameterResolver : injecte les paramètres de tests de type
TestReporter
Extensions fournies
En plus des extensions activée par défaut, les extensions ci-dessous sont fournies par JUnit et utilisables dans nos tests. Elles sont généralement utilisées sous la forme d’une annotation dédiée.
La plus grande famille concerne les annotations conditionnelles.
-
@DisabledForJreRange
,@EnabledForJreRange
-
@DisabledIfEnvironmentVariable
,@EnabledIfEnvironmentVariable
-
@DisabledIfSystemProperty
,@EnabledIfSystemProperty
-
@EnabledForJreRange
,@DisabledForJreRange
-
@DisabledOnOs
,@EnabledOnOs`
-
@DisabledIf
,@EnabledIf
@Test
@DisabledIf("tooLate")
public void testIf() {
System.out.println("Enabled at hour: " + LocalTime.now());
}
private boolean tooLate() {
return LocalTime.now()
.isAfter(LocalTime.of(22, 0));
}
Les conditions peuvent être utilisées sur les méthodes du cycle de vie, pour initialiser le contexte du test en fonction de l’environnement. |
La plus utilisée permet d’exécuter des tests paramétrés.
Elle est dans l’artéfact org.junit.jupiter:junit-jupiter-params
.
-
@ParameterizedTest
Extensions tierce
-
MockitoExtension
, qui peut aussi être utilisé via l’annotation@MockitoSettings
-
@Testcontainers
, qui ne peut pas être remplacée par@ExtendWith
car l’extension vérifie la présence de l’annotation -
SpringExtension
, avec les annotations@SpringJUnitConfig
,@SpringJUnitWebConfig
,@SpringBootTest
Développer une extension
Une extension est une classe qui implémente l’interface Extension
.
Cette interface n’est qu’un marqueur puisqu’elle est vide.
Concrètement, il faut implémenter une de ses sous-interface qui s’intègre dans le cycle de vie de JUnit.
Extension de cycle de vie
Ces extensions permettent de s’intégrer dans le cycle de vie du test et d’y ajouter des comportements. Par exemple, il est classique de démarrer un conteneur de composants ou un serveur avant le démarrage des tests. On peut aussi faire des extensions qui traitent certains types d’exceptions ou qui restituent les résultats de façon personnalisée.
-
BeforeAllCallback
,AfterAllCallback
-
BeforeEachCallback
,AfterEachCallback
-
BeforeTestExecutionCallback
,AfterTestExecutionCallback
-
TestExecutionExceptionHandler
-
TestInstancePostProcessor
-
TestInstancePreDestroyCallback
Exemple :
public class SpringExtension
implements BeforeAllCallback, AfterAllCallback,
TestInstancePostProcessor,
BeforeEachCallback, AfterEachCallback,
BeforeTestExecutionCallback, AfterTestExecutionCallback,
ParameterResolver {
//...
}
Fournisseur de paramètres
Même sans faire des tests paramétrés, on peut déclarer des paramètres à une méthode de test simple.
L’injection de ces paramètre est prise en charge par des extensions de type ParameterResolver
.
C’est aussi valable pour les méthodes du cycle de vie.
Dans l’exemple ci-dessous, la méthode de préparation prend un Instant
en paramètre.
Cet Instant
est injecté par le InstantParameterResolver
.
@ExtendWith(InstantParameterResolver.class)
public class ParameterTest {
@BeforeEach
void setUp(Instant instant) {
System.out.println("@BeforeEach " + instant);
}
//...
}
public class InstantParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return parameterContext.getParameter().getType() == Instant.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return Instant.now();
}
}
Les paramètres de type TestInfo
et TestReporter
sont gérés nativement par JUnit grâce à des extensions par défaut.
Fabrique de test
La catégorie d’extension qui est probablement la moins utilisée est TestInstanceFactory
qui permet d’instancier le test de façon programmatique.
Ça permet d’instancier une sous-classe du test ou de passer des paramètres au constructeur sans avoir recours à un ParameterResolver
.
L’exemple avec l'`Instant` donnerait ceci avec une fabrique :
public class FactoryTest {
@RegisterExtension
static TestInstanceFactory factory
= (factoryContext, extensionContext) -> new FactoryTest(Instant.now().minus(1, ChronoUnit.DAYS));
private final Instant instant;
public FactoryTest(Instant now) {
this.instant = now;
}
@Test
void should_work() {
System.out.println("@Test " + instant);
}
}
Synthèse
L’utilisation d’extensions par l’annotation @ExtendWith
est la plus évidente, mais plusieurs autres usages sont masqués.
Ainsi, les annotations @Timeout
ou @Disabled
viennent d’annotations activées par défaut.
Les annotations de condition, @DisabledXxx
et @EnabledXxx
sont des méta-annotations d’extensions.
Par ailleurs, si l’intérêt d’activer globalement une extension, via le fichier org.junit.jupiter.api.extension.Extension
dans le répertoire /META-INF/services
, ne parait pas évident dans le cas général, on comprend plus facilement lorsqu’il s’agit de résolveur de paramètre ou du support d’une annotation personnalisée.
Bref, les extensions de JUnit 5 sont plus puissantes et bien plus pratiques que ce qui existait dans les versions précédentes.