All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.amazingcode.timeflow.TestTime Maven / Gradle / Ivy

package pl.amazingcode.timeflow;

import static java.lang.Thread.sleep;
import static pl.amazingcode.timeflow.Preconditions.checkArgument;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * TestTime is a singleton class that provides access to the current time and allows to manipulate
 * it both in production and test code. It is an extension of {@link Time}. TestTime is designed to
 * be used in tests only.
 */
public final class TestTime extends Time {

  private static final TestTime TEST_INSTANCE = new TestTime();
  private final List> observers;

  private TestTime() {
    super();
    this.observers = new ArrayList<>();
  }

  /**
   * Returns the singleton instance of TestTime.
   *
   * @return the singleton instance of TestTime
   */
  public static TestTime testInstance() {
    return TEST_INSTANCE;
  }

  /** {@inheritDoc} */
  @Override
  public Instant now() {
    return instance().now();
  }

  /** {@inheritDoc} */
  @Override
  public Clock clock() {
    return instance().clock();
  }

  /**
   * Replace the clock used by {@link Time} and {@link TestTime} with a custom one.
   *
   * 
   *    {@code // Prepare a fixed clock}
   *    {@code ZoneId zoneId = TimeZone.getTimeZone("Europe/Warsaw").toZoneId();}
   *    {@code ZonedDateTime dateTime = LocalDateTime.of(2001, 11, 23, 12, 15).atZone(zoneId);}
   *    {@code Clock fixedClock = Clock.fixed(dateTime.toInstant(), zoneId);}
   *
   *    {@code // Set the fixed clock as the clock used by Time in the production code}
   *    {@code TestTime.testInstance().setClock(fixedClock);}
   * 
* * @param clock */ @Override public synchronized void setClock(Clock clock) { instance().setClock(clock); } /** * Reset the clock used by {@link Time} and {@link TestTime} with current time using {@link * Clock#systemUTC()}. */ public synchronized void resetClock() { instance().setClock(Clock.systemUTC()); } /** * Jump the clock forward by the given duration. * * @param duration the duration to jump forward */ public synchronized void fastForward(Duration duration) { var updatedClock = Clock.offset(instance().clock(), duration); instance().setClock(updatedClock); notifyObservers(); } /** * Jump the clock backward by the given duration. * * @param duration the duration to jump backward */ public synchronized void fastBackward(Duration duration) { var updatedClock = Clock.offset(instance().clock(), duration.negated()); instance().setClock(updatedClock); notifyObservers(); } /** * Simulate time flow by jumping the clock forward by the given step every flowSpeedMillis. This * will replace the clock used by {@link Time} in the production code. * *
   *    {@code // Prepare a fixed clock}
   *    {@code ZoneId zoneId = TimeZone.getTimeZone("Europe/Warsaw").toZoneId();}
   *    {@code ZonedDateTime dateTime = LocalDateTime.of(2001, 11, 23, 12, 15).atZone(zoneId);}
   *    {@code Clock fixedClock = Clock.fixed(dateTime.toInstant(), zoneId);}
   *
   *    {@code // Set the fixed clock as the clock used by Time in the production code}
   *    {@code TestTime.testInstance().setClock(fixedClock);}
   *
   *    {@code // Prepare time flow parameters}
   *    {@code Duration step = Duration.of(1, ChronoUnit.MINUTES);}
   *    {@code Instant endTime = fixedClock.instant().plus(10, ChronoUnit.MINUTES);}
   *    {@code int flowSpeedMillis = 100;}
   *
   *    {@code // Simulate time flow}
   *    {@code TestTime.testInstance().timeFlow(step, endTime, flowSpeedMillis);}
   * 
* * @param step the duration to jump forward * @param endTime the time when the simulated time flow should stop * @param flowSpeedMillis the speed of the simulated time flow in milliseconds * @throws IllegalArgumentException if flowSpeedMillis is not positive or endTime is before the * current time * @throws RuntimeException if interrupted while sleeping between jumps */ public synchronized void timeFlow(Duration step, Instant endTime, int flowSpeedMillis) { checkArgument(flowSpeedMillis > 0, "Flow speed must be positive"); checkArgument(endTime.isAfter(instance().now()), "End time must be after current time"); try { while (instance().now().isBefore(endTime)) { sleep(flowSpeedMillis); fastForward(step); } } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * Register an observer that will be notified every time the clock is changed by {@link * TestTime#fastForward(Duration)}, {@link TestTime#fastBackward(Duration)} or {@link * TestTime#timeFlow(Duration, Instant, int)}. * *
   *    {@code TestTime}
   *        {@code .testInstance()}
   *        {@code .registerObserver(clock -> System.out.println(clock.instant()));}
   * 
* * @param clockConsumer the observer */ public synchronized void registerObserver(Consumer clockConsumer) { observers.add(clockConsumer); } /** Remove all registered observers. */ public synchronized void clearObservers() { observers.clear(); } private void notifyObservers() { observers.forEach(observer -> observer.accept(instance().clock())); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy