
Proces testowania przez wielu z nas traktowany jest podobnie jak sztuka. Dobre testy powinny spełniać wiele czynników by mogły być nazwane “dobrymi”. Jednym z nich jest brak zależność od czasu. Nasze testy powinny być tak szybkie jak to tylko możliwe oraz nie powinny zależeć od czynników zewnętrznych (mówimy o testach jednostkowych). Aby pozbyć się zależności czasowych Java 8 dostarczyła nam nową klasę java.time.Clock.
Zły przykład
Zaczniemy od klasy ClassWhichDependOnTime, która zależy od czasu (przepraszam za Date w Javie 8):
class ClassWhichDependsOnTime {
boolean isGoingToExpireToday(ClassWithFixedTime classWithFixedTime) {
    long result = classWithFixedTime.getDate().getTime() - new Date().getTime();
    return result >= 0 && TimeUnit.MILLISECONDS.toDays(result) == 0;
}
}
W wyniku metody isGoingToExpireToday dostaniemy informację czy dziś wygaśnie ważność klasy ClassWithFixedTime:
class ClassWithFixedTime {
private final Date date;
ClassWithFixedTime(Date date) {
    this.date = date;
}
Date getDate() {
    return date;
}
}
Napiszmy test do tego kodu:
class ClassWhichDependsOnTimeTest {
@Test
void shouldReturnTrueWhenDateIsSameAsToday() {
    // Given
    Date date = DateUtils.addHours(new Date(), 2);
    ClassWithFixedTime fixedTime = new ClassWithFixedTime(date);
    ClassWhichDependsOnTime bad = new ClassWhichDependsOnTime();
    // When
    boolean goingToExpireToday = bad.isGoingToExpireToday(fixedTime);
    // Then
    assertThat(goingToExpireToday).isTrue();
}
}
Jest godzina 10:00 i uruchamiamy nasz test. Wszystko przechodzi poprawnie. Koło godziny 23:00 nasz kolega z zespołu postanawia wrzucić coś do developa, odpalają się testy, a tutaj:
java.lang.AssertionError:
Expecting:
 
to be equal to:
 
but was not.
at ClassWhichDependsOnTimeTest.someTestWhichDependsOnTime(ClassWhichDependsOnTimeTest.java:23)
Niestety, nasze testy nie są dobrymi testami, ponieważ zależą od czasu. Odwróćmy zależność.
Clock na ratunek
Rozwiązaniem tego problemu jest klasa java.time.Clock, która powinna być dostarczona jako zależność:
class GoodClassWhichDependsOnTime {
private final Clock clock;
GoodClassWhichDependsOnTime(Clock clock) {
    this.clock = clock;
}
boolean isGoingToExpireToday(ClassWithFixedTime classWithFixedTime) {
    long result = clock.millis() - classWithFixedTime.getDate().getTime();
    return result >= 0 && TimeUnit.MILLISECONDS.toDays(result) == 0;
}
}
Teraz nasze testy będą wyglądać następująco:
class GoodClassWhichDependsOnTimeTest {
@Test
void shouldReturnTrueWhenDateIsSameAsToday() {
    // Given
    Date date = getExpirationDate(21);
    ClassWithFixedTime fixedTime = new ClassWithFixedTime(date);
    Clock clock = Mockito.mock(Clock.class);
    when(clock.millis()).thenReturn(getCurrentDateInMillis());
    GoodClassWhichDependsOnTime good = new GoodClassWhichDependsOnTime(clock);
    // When
    boolean goingToExpireToday = good.isGoingToExpireToday(fixedTime);
    // Then
    assertThat(goingToExpireToday).isTrue();
}
private Date getExpirationDate(int hour) {
    Calendar expirationDate = Calendar.getInstance();
    expirationDate.set(2018, Calendar.NOVEMBER, 1, hour, 30, 30);
    return expirationDate.getTime();
}
private long getCurrentDateInMillis() {
    Calendar expirationDate = Calendar.getInstance();
    expirationDate.set(2018, Calendar.NOVEMBER, 1, 22, 30, 30);
    return expirationDate.getTimeInMillis();
}
}
Od teraz test ten jest zależny od przekazanych wartości w metodzie when(), a nie czynników zewnętrznych.
Inne metody
Ponadto, klasa Clock dostarcza kilka przydatnych metod:
Clock.systemDefaultZone()- zwraca aktualną datę w systemowej strefie czasowejClock.fixed()- zwraca datę przekazaną jako parametr metody fixedclock.milis()- zwraca datę w milisekundachclock.instant()- zwraca datę jakoInstant
GitHub
Całość jak zawsze na GitHubie.