En fonction des besoins métiers nous devons parfois implémenter des règles de gestions basées sur l'instant courant. Rien d'anormal jusque là mais comment bien gérer ce cas dans nos tests unitaires ? Le but de cet article est de trouver une réponse à ce problème.
Pourquoi une nouvelle API de date en Java 8
L’API existante avait différents problèmes
- pas thread safe et donc potentiellement des problèmes de concurrence
- API assez pauvre
- confusion entre les dates, les heures
- …
- des objets immutables
- les différents systèmes calendaires existants,
- les différentes timezones
- le formatage et le parsing de dates sans avoir à passer par des classes externes
- les différents use cases : parfois nous voulons gérer des périodes, des durées, des dates, des heures…
Instant (joda, jdk)
Représente un point dans la ligne de temps parfait pour gérer les tmestamps “techniques” qui ne sont pas liés à une time zone
DateTime (joda), ZonedDateTime (jdk)
Remplace l’objet Calendar et représente une date dans une timezone
LocalDate (joda, jdk)
Représente une date sans heure et sans time zone
Représente une heure sans notion de date et de time zone
Représente une date/heure sans time zone
Il existe plusieurs autres classes utilitaires pour vous aider à gérer des durées (Duration - joda, jdk), des périodes (Period - joda, jdk), ...
Exemple en JDK8
et en Joda Time le code est légèrement différent
Fixer la date dans les tests Joda Time
JodaTime propose DateTimeUtils, une classe utilitaire très pratique lorsque vous voulez tester une portion de code manipulant une date courante. Cette classe est en fait plus qu’une classe utilisée pour les tests. Tous les objets Joda se basent sur cette classe pour déterminer l'instant courant.
DateTimeUtils expose des méthodes qui permettent de mocker la valeur de l’instant courant dans les tests. Cette implémentation n’a pas été reprise dans la JSR.
Le code exposé dans le précédent paragraphe se teste très simplement en fixant la valeur du currentTimeMillis le temps du test
Comment faire en Java 8
Côté Java, comme je le disais avant la classe DateTimeUtils n’a pas été reprise. En fait si on regarde dans le code l’instant présent en Java se détermine en appelant l’objet java.time.Clock. Par exemple
Vous pouvez aussi si vous le souhaitez utiliser votre propre instance de Clock. Dans la Javadoc c’est cette méthode qui est préconisée en cas de tests
Si on suit donc les préconisations il faudrait reprendre notre code principal
Dans notre test nous pouvons maintenant fixer la date
Voila vous savez maintenant tout pour tester correctement la date courante dans vos tests. Mes exemples pourraient être améliorer en introduisant par exemple une Rule Junit pour réinitialiser automatiquement la date à la fin du test. Si vous utilisez une version du JDK < 8 je vous conseille vivement d’utiliser JodaTime.
Il existe plusieurs autres classes utilitaires pour vous aider à gérer des durées (Duration - joda, jdk), des périodes (Period - joda, jdk), ...
Exemple en JDK8
private Instant eventDate = Instant.parse("2016-04-21T08:30:00.00Z");
public String getCountDownBeforeEvent(){ if(Instant.now().compareTo(eventDate)<0){ return String.format("%s days before our event",
Duration.between(Instant.now(), eventDate).toDays()); } return String.format("Event closed since %s days",
Duration.between(eventDate, Instant.now()).toDays());
}
et en Joda Time le code est légèrement différent
public String getCountDownBeforeEvent() { if (Instant.now().compareTo(eventDate) < 0) { return String.format("%s days before our event", new Duration(Instant.now().getMillis(), eventDate.getMillis())
.toStandardDays().getDays()); } return String.format("Event closed since %s days", new Duration(eventDate.getMillis(), Instant.now().getMillis())
.toStandardDays().getDays());
}
Fixer la date dans les tests Joda Time
JodaTime propose DateTimeUtils, une classe utilitaire très pratique lorsque vous voulez tester une portion de code manipulant une date courante. Cette classe est en fait plus qu’une classe utilisée pour les tests. Tous les objets Joda se basent sur cette classe pour déterminer l'instant courant.
DateTimeUtils expose des méthodes qui permettent de mocker la valeur de l’instant courant dans les tests. Cette implémentation n’a pas été reprise dans la JSR.
Le code exposé dans le précédent paragraphe se teste très simplement en fixant la valeur du currentTimeMillis le temps du test
public class ServiceJodaTimeTest { private ServiceJodaTime service = new ServiceJodaTime(); @After public void tearDown(){ DateTimeUtils.setCurrentMillisSystem(); } @Test public void should_test_a_date_before_event() { DateTimeUtils.setCurrentMillisFixed(Instant.parse("2016-04-18T00:00:00.00Z").getMillis()); Assertions.assertThat(service.getCountDownBeforeEvent()).isEqualTo("3 days before our event"); } @Test public void should_test_a_date_after_event() { DateTimeUtils.setCurrentMillisFixed(Instant.parse("2016-04-30T00:00:00.00Z").getMillis()); Assertions.assertThat(service.getCountDownBeforeEvent()).isEqualTo("Event closed since 8 days"); } }
Comment faire en Java 8
Côté Java, comme je le disais avant la classe DateTimeUtils n’a pas été reprise. En fait si on regarde dans le code l’instant présent en Java se détermine en appelant l’objet java.time.Clock. Par exemple
public static Instant now() { return Clock.systemUTC().instant(); }
Vous pouvez aussi si vous le souhaitez utiliser votre propre instance de Clock. Dans la Javadoc c’est cette méthode qui est préconisée en cas de tests
/** * Obtains the current instant from the specified clock. * <p> * This will query the specified clock to obtain the current time. * <p> * Using this method allows the use of an alternate clock for testing. * The alternate clock may be introduced using {@link Clock dependency injection}. * * @param clock the clock to use, not null * @return the current instant, not null */ public static Instant now(Clock clock) { Objects.requireNonNull(clock, "clock"); return clock.instant(); }
Si on suit donc les préconisations il faudrait reprendre notre code principal
private Clock clock = Clock.systemUTC(); public String getCountDownBeforeEvent2(){
if(Instant.now(clock).compareTo(eventDate)<0){ return String.format("%s days before our event",
Duration.between(Instant.now(clock), eventDate).toDays()); } return String.format("Event closed since %s days",
Duration.between(eventDate, Instant.now(clock)).toDays());
}
Dans notre test nous pouvons maintenant fixer la date
public class ServiceJava8Test { private ServiceJava8 service = new ServiceJava8(); @After public void tearDown(){ service.setClock(Clock.systemUTC()); } @Test public void should_test_a_date_before_event() { service.setClock(Clock.fixed(Instant.parse("2016-04-18T00:00:00.00Z"), ZoneId.systemDefault())); Assertions.assertThat(service.getCountDownBeforeEvent2()).isEqualTo("3 days before our event"); } @Test public void should_test_a_date_after_event() { service.setClock(Clock.fixed(Instant.parse("2016-04-30T00:00:00.00Z"), ZoneId.systemDefault())); Assertions.assertThat(service.getCountDownBeforeEvent2()).isEqualTo("Event closed since 8 days"); } }
Voila vous savez maintenant tout pour tester correctement la date courante dans vos tests. Mes exemples pourraient être améliorer en introduisant par exemple une Rule Junit pour réinitialiser automatiquement la date à la fin du test. Si vous utilisez une version du JDK < 8 je vous conseille vivement d’utiliser JodaTime.
Tout le code présenté ici est disponible sous Github
Aucun commentaire:
Enregistrer un commentaire
Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.