Żeby pobrać aktualną datę można wykonać Instant.now()
, bardzo szybkie i daje aktualną godzinę, jaka jest na zegarku systemowym.
Ale to rozwiązanie jest nietestowalne, nie da się przewidzieć w teście co .now()
zwróci, nie da się wykonać asercji. Natomiast niektórzy próbują mockować tę statyczną metodę powermockiem. Proste, prawie czytelne, śmierdzi fest.
Jaka ma całkiem fajną klasę Clock
, można wykonać clock.instant()
i będzie aktualny czas. W kodzie produkcyjnym można wstrzyknąć systemowy zegarek, a w teście Clock.fixed(...)
, wtedy w teście możemy przewidzieć jaki czas zostanie zwrócony. Fajnie, bo można dobre asercje robić. Ale trzeba jakieś cloicki wstrzykiwać, dodatkowe zależności, dodatkowe beany produkcyjne, dodatkowe beany w testach integracyjnych. Można pobrać godzinę, ale nie można sterować, nie da się w teście integracyjnym zasymulować upływu czasu.
W niektórych projektach widzę, że dodany jest interfejs
public interface TimeProvider {
Instant instant();
}
(identyczny interfejs dostarcza java od jakiegoś czasu, InstantSource). W produkcyjnym kodzie jest tam Clock, a w testowym taki bean
public class FakeTimeProvider implements TimeProvider {
private Clock clock;
public TestTimeProvider() {
this(Instant.parse("2021-04-11T13:00:00.000Z"));
}
public TestTimeProvider(Instant initInstant) {
this.clock = buildClock(initInstant);
}
@Override
public Instant instant() {
return clock.instant();
}
public void plus(TemporalAmount temporalAmount) {
clock = buildClock(instant().plus(temporalAmount));
}
public void plus(long amountToAdd, TemporalUnit unit) {
clock = buildClock(instant().plus(amountToAdd, unit));
}
private Clock buildClock(Instant instant) {
return Clock.fixed(instant, ZoneId.of("UTC"));
}
}
Co pozwala robić rewelacyjne testy integracyjne
@Test
void test_stm() {
//...
// when
timeProvider.plus(5, DAYS);
// then
assertThat(...)
}
Możemy w teście integracyjno behawioralnym przetestować, że coś się stało za kilka dni. Rewelacyjna sprawa. Ale robi się z tego typowa Java, interfejs co przykrywa interfejs i niepotrzbne implementacja, wstrzykiwanie beanów których można nie wsytrzykiwać bo można mocka na Instant.now() zrobić.
Jak ten problem się rozwiązuje u Was?