Guide des règles JUnit 4

1. Vue d'ensemble

Dans ce tutoriel, nous allons jeter un œil à la fonctionnalité Rules fournie par la bibliothèque JUnit 4.

Nous commencerons par présenter le modèle de règles JUnit avant de parcourir les règles de base les plus importantes fournies par la distribution. De plus, nous verrons également comment écrire et utiliser notre propre règle JUnit personnalisée.

Pour en savoir plus sur les tests avec JUnit, consultez notre série complète de JUnit.

Notez que si vous utilisez JUnit 5, les règles ont été remplacées par le modèle Extension.

2. Introduction aux règles JUnit 4

Les règles JUnit 4 fournissent un mécanisme flexible pour améliorer les tests en exécutant du code autour de l'exécution d'un cas de test . Dans un certain sens, c'est similaire aux annotations @Before et @After dans notre classe de test.

Imaginons que nous voulions nous connecter à une ressource externe telle qu'une base de données lors de la configuration du test, puis fermer la connexion une fois notre test terminé. Si nous voulons utiliser cette base de données dans plusieurs tests, nous finirions par dupliquer ce code dans chaque test.

En utilisant une règle, nous pouvons tout isoler au même endroit et réutiliser facilement le code à partir de plusieurs classes de test.

3. Utilisation des règles JUnit 4

Alors, comment pouvons-nous utiliser des règles? Nous pouvons utiliser les règles JUnit 4 en suivant ces étapes simples:

  • Ajoutez un champ public à notre classe de test et assurez-vous que le type de ce champ est un sous-type de l' interface org.junit.rules.TestRule
  • Annoter le champ avec l' annotation @Rule

Dans la section suivante, nous verrons de quelles dépendances de projet nous avons besoin pour démarrer.

4. Dépendances de Maven

Tout d'abord, ajoutons les dépendances de projet dont nous aurons besoin pour nos exemples. Nous n'aurons besoin que de la bibliothèque principale JUnit 4:

 junit junit 4.12  

Comme toujours, nous pouvons obtenir la dernière version de Maven Central.

5. Règles fournies dans la distribution

Bien entendu, JUnit fournit un certain nombre de règles utiles et prédéfinies dans le cadre de la bibliothèque . Nous pouvons trouver toutes ces règles dans le package org.junit.rules .

Dans cette section, nous verrons quelques exemples de leur utilisation.

5.1. La règle TemporaryFolder

Lors des tests, nous avons souvent besoin d'accéder à un fichier ou à un dossier temporaire. Cependant, la gestion de la création et de la suppression de ces fichiers peut être fastidieuse. En utilisant la règle TemporaryFolder , nous pouvons gérer la création de fichiers et de dossiers qui doivent être supprimés à la fin de la méthode de test :

@Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); @Test public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException { File testFile = tmpFolder.newFile("test-file.txt"); assertTrue("The file should have been created: ", testFile.isFile()); assertEquals("Temp folder and test file should match: ", tmpFolder.getRoot(), testFile.getParentFile()); }

Comme nous pouvons le voir, nous définissons d'abord la règle TemporaryFolder tmpFolder . Ensuite, notre méthode de test crée un fichier appelé test-file.txt dans le dossier temporaire. Nous vérifions ensuite que le fichier a été créé et existe là où il le devrait. Vraiment sympa et simple!

Une fois le test terminé, le dossier et le fichier temporaires doivent être supprimés. Cependant, cette règle ne vérifie pas si la suppression est réussie ou non.

Il y a aussi quelques autres méthodes intéressantes à mentionner dans cette classe:

  • newFile()

    Si nous ne fournissons aucun nom de fichier, cette méthode crée un nouveau fichier nommé aléatoirement.

  • newFolder(String... folderNames)

    Pour créer des dossiers temporaires profonds de manière récursive, nous pouvons utiliser cette méthode.

  • newFolder()

    De même, la méthode newFolder () crée un nouveau dossier nommé aléatoirement.

Un ajout intéressant à mentionner est qu'à partir de la version 4.13, la règle TemporaryFolder permet la vérification des ressources supprimées:

@Rule public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();

Si une ressource ne peut pas être supprimée, le test échoue avec une AssertionError .

Finally, in JUnit 5, we can achieve the same functionality using the Temporary Directory extension.

5.2. The ExpectedException Rule

As the name suggests, we can use the ExpectedException rule to verify that some code throws an expected exception:

@Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void givenIllegalArgument_whenExceptionThrown_MessageAndCauseMatches() { thrown.expect(IllegalArgumentException.class); thrown.expectCause(isA(NullPointerException.class)); thrown.expectMessage("This is illegal"); throw new IllegalArgumentException("This is illegal", new NullPointerException()); }

As we can see in the example above, we’re first declaring the ExpectedException rule. Then, in our test, we’re asserting that an IllegalArgumentException is thrown.

Using this rule, we can also verify some other properties of the exception, such as the message and cause.

For an in-depth guide to testing exceptions with JUnit, check out our excellent guide on how to Assert an Exception.

5.3. The TestName Rule

Put simply, the TestName rule provides the current test name inside a given test method:

@Rule public TestName name = new TestName(); @Test public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() { LOG.info("Executing: {}", name.getMethodName()); assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName()); }

In this trivial example, when we run the unit test, we should see the test name in the output:

INFO c.baeldung.rules.JUnitRulesUnitTest - Executing: givenAddition_whenPrintingTestName_thenTestNameIsDisplayed

5.4. The Timeout Rule

In this next example, we'll take a look at the Timeout rule. This rule offers a useful alternative to using the timeout parameter on an individual Test annotation.

Now, let's see how to use this rule to set a global timeout on all the test methods in our test class:

@Rule public Timeout globalTimeout = Timeout.seconds(10); @Test public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException { TimeUnit.SECONDS.sleep(20); }

In the above trivial example, we first define a global timeout for all test methods of 10 seconds. Then we deliberately define a test which will take longer than 10 seconds.

When we run this test, we should see a test failure:

org.junit.runners.model.TestTimedOutException: test timed out after 10 seconds ...

5.5. The ErrorCollector Rule

Next up we're going to take a look at the ErrorCollector rule. This rule allows the execution of a test to continue after the first problem is found.

Let's see how we can use this rule to collect all the errors and report them all at once when the test terminates:

@Rule public final ErrorCollector errorCollector = new ErrorCollector(); @Test public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() { errorCollector.addError(new Throwable("First thing went wrong!")); errorCollector.addError(new Throwable("Another thing went wrong!")); errorCollector.checkThat("Hello World", not(containsString("ERROR!"))); }

In the above example, we add two errors to the collector. When we run the test, the execution continues, but the test will fail at the end.

In the output, we will see both errors reported:

java.lang.Throwable: First thing went wrong! ... java.lang.Throwable: Another thing went wrong!

5.6. The Verifier Rule

The Verifier rule is an abstract base class that we can use when we wish to verify some additional behavior from our tests. In fact, the ErrorCollector rule we saw in the last section extends this class.

Let's now take a look at a trivial example of defining our own verifier:

private List messageLog = new ArrayList(); @Rule public Verifier verifier = new Verifier() { @Override public void verify() { assertFalse("Message Log is not Empty!", messageLog.isEmpty()); } }; 

Here, we define a new Verifier and override the verify() method to add some extra verification logic. In this straightforward example, we simply check to see that the message log in our example isn't empty.

Now, when we run the unit test and add a message, we should see that our verifier has been applied:

@Test public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() { // ... messageLog.add("There is a new message!"); }

5.7. The DisableOnDebug Rule

Sometimes we may want to disable a rule when we're debugging. For example, it’s often desirable to disable a Timeout rule when debugging to avoid our test timing out and failing before we've had time to debug it properly.

The DisableOnDebug Rule does precisely this and allows us to label certain rules to be disabled when debugging:

@Rule public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));

In the example above we can see that in order to use this rule, we simply pass the rule we want to disable to the constructor.

The main benefit of this rule is that we can disable rules without making any modifications to our test classes during debugging.

5.8. The ExternalResource Rule

Typically, when writing integration tests, we may wish to set up an external resource before a test and tear it down afterward. Thankfully, JUnit provides another handy base class for this.

We can extend the abstract class ExternalResource to set up an external resource before a test, such as a file or a database connection. In fact, the TemporaryFolder rule we saw earlier extends ExternalResource.

Let's take a quick look at how we could extend this class:

@Rule public final ExternalResource externalResource = new ExternalResource() { @Override protected void before() throws Throwable { // code to set up a specific external resource. }; @Override protected void after() { // code to tear down the external resource }; };

In this example, when we define an external resource we simply need to override the before() method and after() method in order to set up and tear down our external resource.

6. Applying Class Rules

Up until now, all the examples we've looked at have applied to single test case methods. However, sometimes we might want to apply a rule at the test class level. We can accomplish this by using the @ClassRule annotation.

This annotation works very similarly to @Rule but wraps a rule around a whole test — the main difference being that the field we use for our class rule must be static:

@ClassRule public static TemporaryFolder globalFolder = new TemporaryFolder();

7. Defining a Custom JUnit Rule

As we've seen, JUnit 4 provides a number of useful rules out of the box. Of course, we can define our own custom rules. To write a custom rule, we need to implement the TestRule interface.

Let's take a look at an example of defining a custom test method name logger rule:

public class TestMethodNameLogger implements TestRule { private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class); @Override public Statement apply(Statement base, Description description) { logInfo("Before test", description); try { return new Statement() { @Override public void evaluate() throws Throwable { base.evaluate(); } }; } finally { logInfo("After test", description); } } private void logInfo(String msg, Description description) { LOG.info(msg + description.getMethodName()); } }

As we can see, the TestRule interface contains one method called apply(Statement, Description) that we must override to return an instance of Statement. The statement represents our tests within the JUnit runtime. When we call the evaluate() method, this executes our test.

In this example, we log a before and after message and include from the Description object the method name of the individual test.

8. Using Rule Chains

In this final section, we'll take a look at how we can order several test rules using the RuleChain rule:

@Rule public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule")) .around(new MessageLogger("Second rule")) .around(new MessageLogger("Third rule"));

Dans l'exemple ci-dessus, nous créons une chaîne de trois règles qui affichent simplement le message passé à chaque constructeur MessageLogger .

Lorsque nous exécuterons notre test, nous verrons comment la chaîne est appliquée dans l'ordre:

Starting: First rule Starting: Second rule Starting: Third rule Finished: Third rule Finished: Second rule Finished: First rule

9. Conclusion

Pour résumer, dans ce tutoriel, nous avons exploré en détail les règles JUnit 4.

Tout d'abord, nous avons commencé par expliquer ce que sont les règles et comment nous pouvons les utiliser. Ensuite, nous avons examiné en profondeur les règles qui font partie de la distribution JUnit.

Enfin, nous avons examiné comment nous pouvons définir notre propre règle personnalisée et comment enchaîner les règles.

Comme toujours, le code source complet de l'article est disponible à l'adresse over sur GitHub.