Contents
Time is volatile
Imagine writing a cron-like functionality that should produce some side-effect, such as cleanup. The intervals between such actions might be quite long. How does one test that? One can surely reason about the software, but given a certain complexity, test should be written, proving that certain important scenarios work as intended.
It’s common that software depends on time flow as dictated by the physical time flow, reflected via some clock provider. However, resetting the time to a year ahead won’t make the CPU work faster and make all the computations it should have performed within that year. A clock is also a volatile component that can be manipulated, thus if time is an issue, it’s probably a good idea not to depend on it directly, following the Stable Dependencies Principle and the Dependency Inversion Principle.
Luckily, there is an abstraction for time, at least in Reactive Extensions (Rx), which is the Scheduler.
Slow non-tests
Here’s a slow Groovy non-test, waiting for some output on the console using RxGroovy:
import rx.* import java.util.concurrent.TimeUnit def observable = Observable .just(1) .delay(5, TimeUnit.SECONDS) observable.subscribe { println 'ah, OK, done! Or not?' } Observable .interval(1,TimeUnit.SECONDS) .subscribe { println 'still waiting...' } println 'starting to wait for the test to complete ...' observable.toBlocking().last()
Running it produces the following slow-ticking output:
[1. Caputured with the wonderful pragmatic tool LICEcap by the Reaper developers]
Interpreting such tests without color can be somewhat challenging [2. Here, the ‘still waiting’ subscription is terminated after the first subscription ends. Try exchanging the order of the subscribe calls.].
Fast tests
Now let’s test something ridiculous, such as waiting for a hundred days using Spock. Luckily, RxJava & RxGroovy also do implement the test scheduler, thus enabling fast tests using virtual time:
import spock.lang.Specification import rx.Observable import rx.schedulers.TestScheduler import java.util.concurrent.TimeUnit class DontWaitForever extends Specification { def "why wait?"() { setup: def scheduler = new TestScheduler() // system under test: will tick once after a hundred days def observable = Observable .just(1) .delay(100, TimeUnit.DAYS, scheduler) def done = false when: observable.subscribe { done = true } // still in the initial state done == false and: scheduler.advanceTimeBy 100, TimeUnit.DAYS then: done == true } }
[3. Building using Gradle]
just checking, advancing the time by 99 days results in a failure:
Delightful, groovy colors!