Remplacement de l'heure système pour les tests en Java

1. Vue d'ensemble

Dans ce rapide didacticiel, nous allons nous concentrer sur différentes façons de remplacer l'heure du système pour les tests .

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

Pour une introduction à l'utilisation de Java Clock , veuillez consulter cet article ici. Ou, à l'utilisation d'AspectJ, ici.

2. Utilisation de l'horloge dans java.time

Le package java.time de Java 8 inclut une classe abstraite 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, la bibliothèque ci-dessus inclut des méthodes statiques pour produire des implémentations spéciales . Nous allons en utiliser deux qui retournent une implémentation immuable, thread-safe et sérialisable.

Le premier est fixe . À partir de là, nous pouvons obtenir une horloge qui renvoie toujours le même instant , garantissant que les tests ne dépendent pas de l'horloge actuelle.

Pour l'utiliser, nous avons besoin d'un Instant et d'un ZoneOffset :

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

La deuxième méthode statique est le décalage . Dans celui-ci, une horloge encapsule une autre horloge qui en fait l'objet retourné capable d'obtenir des instants plus tard ou plus tôt de la durée spécifiée.

En d'autres termes, il est possible de simuler une exécution dans le futur, dans le passé ou à n'importe quel moment arbitraire :

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 classe Duration , il est possible de manipuler des nanosecondes aux jours. De plus, nous pouvons annuler une durée, ce qui signifie obtenir une copie de cette durée avec la longueur annulée.

3. Utilisation de la programmation orientée aspect

Une autre façon de remplacer l'heure du système est par AOP. Avec cette approche, nous sommes en mesure de tisser la classe System pour renvoyer une valeur prédéfinie que nous pouvons définir dans nos cas de test .

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

Une façon de l'implémenter 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, nous interceptons chaque appel à System.currentTimeMillis () dans un package spécifié , qui dans ce cas est user.code.base.pckg. * , Et retournons 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.

Un avantage d'utiliser AspectJ est qu'il fonctionne directement au niveau du bytecode, il n'a donc pas besoin du code source d'origine pour fonctionner.

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

4. Mocking la Instant.now () Méthode

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

Voyons quelques alternatives pour changer son comportement lorsque nous testons.

4.1. Surcharge maintenant () avec une horloge

Nous pouvons surcharger la méthode now () avec une instance d' horloge fixe . De nombreuses classes du package java.time ont une méthode now () qui prend un paramètre Clock , 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 de PowerMock

De plus, si nous devons modifier le comportement de la méthode now () sans envoyer de paramètres, nous pouvons utiliser PowerMock :

@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. Utilisation de JMockit

Alternativement, nous pouvons utiliser la bibliothèque JMockit .

JMockit nous offre deux façons de se moquer d'une méthode statique. L'un utilise la classe MockUp :

@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 classe Expectations :

@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. Mocking la LocalDateTime.now () Méthode

Une autre classe utile du package java.time est la classe LocalDateTime . Cette classe représente une date-heure sans fuseau horaire dans le système de calendrier ISO-8601. La méthode now () 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 en moquer que nous l'avons vu auparavant. Par exemple, surcharger now () avec une horloge 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 façons de remplacer l'heure du système pour les tests. Tout d'abord, nous avons examiné le package natif java.time et sa classe Clock . Ensuite, nous avons vu comment appliquer un aspect pour tisser la classe System . Enfin, nous avons vu différentes alternatives pour se moquer de la méthode now () sur les classes Instant et LocalDateTime .

Comme toujours, des exemples de code peuvent être trouvés sur GitHub.