140 votes

Comment faire attendre un test JUnit ?

J'ai un test JUnit que je veux faire attendre pendant une période de temps de manière synchrone. Mon test JUnit ressemble à ceci :

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    // WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExipred("foo"));
}

J'ai essayé Thread.currentThread().wait() mais le système génère un IllegalMonitorStateException (comme prévu).

Y a-t-il une astuce ou ai-je besoin d'un autre écran ?

10voto

Philzen Points 858

Mockito (qui est déjà fourni via des dépendances transitives pour les projets Spring Boot) dispose de plusieurs moyens d'attendre que des événements asynchrones, respectivement des conditions, se produisent.

Un modèle simple qui fonctionne actuellement très bien pour nous est le suivant :

// ARRANGE – instantiate Mocks, setup test conditions

// ACT – the action to test, followed by:
Mockito.verify(myMockOrSpy, timeout(5000).atLeastOnce()).delayedStuff();
// further execution paused until `delayedStuff()` is called – or fails after timeout

// ASSERT – assertThat(...)

Deux autres légèrement plus complexes mais plus sophistiquées sont décrites dans cet article por @fernando-cejas


Mon conseil urgent concernant les meilleures réponses actuelles données ici : vous voulez que vos tests

  1. finir aussi vite que possible
  2. obtenir des résultats cohérents, indépendamment de l'environnement d'essai (non "bancal")

... alors ne faites pas l'idiot en utilisant Thread.sleep() dans votre code de test.

Au lieu de cela, faites en sorte que votre code de production utilise l'injection de dépendances (ou, de manière un peu plus "sale", exposez quelques méthodes mockables/spyables) puis utilisez Mockito, En attente de , ConcurrentUnit ou autres pour s'assurer que les conditions préalables asynchrones sont remplies avant que les assertions ne se produisent.

7voto

Pierre Voisin Points 61

Vous pouvez également utiliser le CountDownLatch comme expliqué aquí .

3voto

Dávid Horváth Points 1077

Il y a un problème général : il est difficile de se moquer du temps. De plus, c'est une très mauvaise pratique de placer du code qui s'exécute ou qui attend depuis longtemps dans un test unitaire.

Ainsi, pour rendre une API de planification testable, j'ai utilisé une interface avec une implémentation réelle et une implémentation fantaisie comme ceci :

public interface Clock {

    public long getCurrentMillis();

    public void sleep(long millis) throws InterruptedException;

}

public static class SystemClock implements Clock {

    @Override
    public long getCurrentMillis() {
        return System.currentTimeMillis();
    }

    @Override
    public void sleep(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }

}

public static class MockClock implements Clock {

    private final AtomicLong currentTime = new AtomicLong(0);

    public MockClock() {
        this(System.currentTimeMillis());
    }

    public MockClock(long currentTime) {
        this.currentTime.set(currentTime);
    }

    @Override
    public long getCurrentMillis() {
        return currentTime.addAndGet(5);
    }

    @Override
    public void sleep(long millis) {
        currentTime.addAndGet(millis);
    }

}

Avec cela, vous pourriez imiter le temps dans votre test :

@Test
public void testExpiration() {
    MockClock clock = new MockClock();
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExpiration("foo", 1000);
    clock.sleep(2000) // wait for 2 seconds
    assertNull(sco.getIfNotExpired("foo"));
}

Un simulateur de multithreading avancé pour Clock est beaucoup plus complexe, bien sûr, mais vous pouvez le faire avec ThreadLocal et une bonne stratégie de synchronisation du temps, par exemple.

2voto

Akhil Ghatiki Points 325

Utiliser Thread.sleep dans un test n'est pas une bonne pratique. Cela crée des tests fragiles qui peuvent échouer de manière imprévisible en fonction de l'environnement ("Passe sur ma machine !") ou de la charge. Ne vous fiez pas au timing (utilisez des mocks) ou utilisez des bibliothèques telles que Awaitility pour les tests asynchrones.

Dependency : testImplementation 'org.awaitility:awaitility:3.0.0'

await().pollInterval(Duration.FIVE_SECONDS).atLeast(Duration.FIVE_SECONDS).atMost(Duration.FIVE_SECONDS).untilAsserted(() -> {
      // your assertion
    });

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X