Remplacement du temps système pour les tests en Java

Remplacement du temps système pour les tests en Java

1. Vue d'ensemble

Dans ce rapide didacticiel, nous allons nous concentrer surdifferent ways to override the system time for testing.

Parfois, il y a une logique autour de la date actuelle dans notre code. Peut-être que certains appels de fonction tels quenew Date() ouCalendar.getInstance(), qui finiront par appelerSystem.CurrentTimeMillis.

Pour une introduction à l'utilisation deJava Clock, veuillez vous référer àto this article here. Ou, à l'utilisation d'AspectJ,here.

2. Utilisation de l'horloge enjava.time

Le packagejava.time dansJava 8 includes an abstract class java.time.Clock dans le but de permettre le branchement d'horloges alternatives en cas de besoin. Avec cela, nous pouvons brancher notre propre implémentation ou en trouver une qui est déjà faite pour satisfaire nos besoins.

Pour atteindre nos objectifs,the above library includes static methods to yield special implementations. Nous allons en utiliser deux qui retournent une implémentation immuable, thread-safe et sérialisable.

Le premier estfixed. À partir de là,we can obtain a Clock that always returns the same*Instant*, détecte que les tests ne dépendent pas de l’horloge actuelle.

Pour l'utiliser, nous avons besoin d'unInstant et d'unZoneOffset:

Instant.now(Clock.fixed(
  Instant.parse("2018-08-22T10:00:00Z"),
  ZoneOffset.UTC))

The second static method is offset. Dans celui-ci, une horloge enveloppe une autre horloge qui en fait l'objet renvoyé capable d'obtenir des instants plus tard ou plus tôt de la durée spécifiée.

En d'autres termes,it’s possible to simulate running in the future, in the past, or in any arbitrary point in time:

Clock constantClock = Clock.fixed(ofEpochMilli(0), ZoneId.systemDefault());

// go to the future:
Clock clock = Clock.offset(constantClock, Duration.ofSeconds(10));

// rewind back with a negative value:
clock = Clock.offset(constantClock, Duration.ofSeconds(-5));

// the 0 duration returns to the same clock:
clock = Clock.offset(constClock, Duration.ZERO);

Avec la classeDuration, il est possible de manipuler de la nanoseconde à quelques jours. De plus, nous pouvons nier une durée, ce qui signifie obtenir une copie de cette durée avec la longueur niée.

3. Utilisation de la programmation par aspect

Une autre façon de remplacer l'heure système est d'utiliser AOP. Avec cette approche,we’re able to weave the System class to return a predefined value which we can set within our test cases.

De plus, il est possible de tisser les classes d’application pour rediriger l’appel versSystem.currentTimeMillis() ou versnew Date() vers une autre classe d’utilitaire de notre choix.

Une façon de mettre cela en œuvre consiste à utiliser AspectJ:

public aspect ChangeCallsToCurrentTimeInMillisMethod {
    long around():
      call(public static native long java.lang.System.currentTimeMillis())
        && within(user.code.base.pckg.*) {
          return 0;
      }
}

Dans l'exemple ci-dessus,we’re catching every call to System.currentTimeMillis() inside a specified package, qui dans ce cas estuser.code.base.pckg., * et renvoie zéro à chaque fois que cet événement se produit.

C'est à cet endroit que nous pouvons déclarer notre propre implémentation pour obtenir le temps souhaité en millisecondes.

L’un des avantages d’utiliser AspectJ est qu’il fonctionne directement au niveau du bytecode, de sorte qu’il n’a pas besoin du code source original pour fonctionner.

Pour cette raison, nous n’aurions pas besoin de le recompiler.

4. Se moquer de la méthodeInstant.now()

Nous pouvons utiliser la classeInstant pour représenter un point instantané sur la timeline. Normalement, nous pouvons l'utiliser pour enregistrer les horodatages des événements dans notre application. La méthodenow() de cette classe nous permet d'obtenir l'instant courant à partir de l'horloge système dans le fuseau horaire UTC.

Voyons quelques alternatives pour modifier son comportement lors des tests.

4.1. Surcharge denow() avec unClock

Nous pouvons surcharger la méthodenow() avec une instanceClock fixe. Many of the classes in the java.time package have a now() method that takes a Clock parameter, ce qui en fait notre approche préférée:

@Test
public void givenFixedClock_whenNow_thenGetFixedInstant() {
    String instantExpected = "2014-12-22T10:15:30Z";
    Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));

    Instant instant = Instant.now(clock);

    assertThat(instant.toString()).isEqualTo(instantExpected);
}

4.2. Utilisation dePowerMock

De plus, si nous devons modifier le comportement de la méthodenow() sans envoyer de paramètres, nous pouvons utiliserPowerMock:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Instant.class })
public class InstantUnitTest {
    @Test
    public void givenInstantMock_whenNow_thenGetFixedInstant() {
        String instantExpected = "2014-12-22T10:15:30Z";
        Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
        Instant instant = Instant.now(clock);
        mockStatic(Instant.class);
        when(Instant.now()).thenReturn(instant);

        Instant now = Instant.now();

        assertThat(now.toString()).isEqualTo(instantExpected);
    }
}

4.3. En utilisant JMockit

Alternativement, nous pouvons utiliser la bibliothèqueJMockit.

JMockit nous offre deux manières democking a static method. L'un utilise la classeMockUp:

@Test
public void givenInstantWithJMock_whenNow_thenGetFixedInstant() {
    String instantExpected = "2014-12-21T10:15:30Z";
    Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
    new MockUp() {
        @Mock
        public Instant now() {
            return Instant.now(clock);
        }
    };

    Instant now = Instant.now();

    assertThat(now.toString()).isEqualTo(instantExpected);
}

Et l'autre utilise la classeExpectations:

@Test
public void givenInstantWithExpectations_whenNow_thenGetFixedInstant() {
    Clock clock = Clock.fixed(Instant.parse("2014-12-23T10:15:30.00Z"), ZoneId.of("UTC"));
    Instant instantExpected = Instant.now(clock);
    new Expectations(Instant.class) {
        {
            Instant.now();
            result = instantExpected;
        }
    };

    Instant now = Instant.now();

    assertThat(now).isEqualTo(instantExpected);
}

5. Se moquer de la méthodeLocalDateTime.now()

Une autre classe utile dans le packagejava.time est la classeLocalDateTime. Cette classe représente une date-heure sans fuseau horaire dans le système de calendrier ISO-8601. La méthodenow() de cette classe nous permet d'obtenir la date-heure actuelle à partir de l'horloge système dans le fuseau horaire par défaut.

Nous pouvons utiliser les mêmes alternatives pour nous moquer que nous avons vu auparavant. Par exemple, surcharge denow() avec unClock fixe:

@Test
public void givenFixedClock_whenNow_thenGetFixedLocalDateTime() {
    Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC"));
    String dateTimeExpected = "2014-12-22T10:15:30";

    LocalDateTime dateTime = LocalDateTime.now(clock);

    assertThat(dateTime).isEqualTo(dateTimeExpected);
}

6. Conclusion

Dans cet article, nous avons exploré différentes manières de remplacer l’heure système pour les tests. Tout d'abord, nous avons examiné le package natifjava.time et sa classeClock. Ensuite, nous avons vu comment appliquer un aspect pour tisser la classeSystem. Enfin, nous avons vu différentes alternatives pour se moquer de la méthodenow() sur les classesInstant etLocalDateTime.

Comme toujours, des échantillons de code peuvent être trouvésover on GitHub.