Expand description
§clocks - testable time
This crate provides a way of making code that depends on time
testable. In your code, instead of calling out to
Utc::now
or similar, you instead use
Clock::now
. When your program is running normally in
production, it will behave just the same as without using this
crate.
§Basic usage
When you need to run tests on your program, you can replace the clock object with a fake clock that lets you control the passage of time. This can be very helpful if you have logic that depends on the passage of time (e.g. if you expect to time things out after minutes or days, and want to test that works).
The mechanism for this is the Clock
struct, which can be
either a wall clock or a fake clock, and has an associated
timezone. Your production code needs to be able to accept a clock
so that tests can override the default wall clock.
Example:
use clocks::Clock;
use chrono::Utc;
pub fn production() {
production_with_clock(Default::default())
}
pub(crate) fn production_with_clock(clock: Clock<Utc>) {
let start = clock.now();
// ...
}
#[test]
fn test_basic() {
let c = Clock::new_fake(Utc::now());
production_with_clock(c.clone());
}
§Sleeping
This crate can also help you test sleep loops, where you want in your test to synchronise with code that sleeps regularly. This is much less hit and miss than injecting sleeps into your test to try to match the sleeping pattern of the code under test, but it can take some effort to get right.
It works only when you have multiple threads or tasks. Give each
thread or task that needs to sleep a “split” of the clock using
Clock::split
. The test thread needs its own split also. Now
Clock::advance
will wait for all other splits to be blocking
in Clock::sleep
.
It is easier to make mistakes than would be ideal, but your
production code will be unaffected by using this functionality in
your tests. Your tests may fail due to errors in usage here, but
none of the test code has any effect in production. The most
likely error is failing to call Clock::split
instead of
Clock::clone
in some place where you intend to hand the clone
to some other thread.
Effectively, we end up needing the clock to be properly split
for each thread, and we can neither find out when our clock was transferred
to another thread or task, nor is there any equivalent of Send
for async code
meaning we cannot prevent clones being shared.
Example:
use clocks::Clock;
use chrono::{Duration, Utc};
pub fn production() {
production_with_clock(Default::default())
}
pub fn production_with_clock(mut clock: Clock<Utc>) {
let time = clock.now();
clock.sleep_blocking(Duration::seconds(10));
assert_eq!(clock.now(), time + Duration::seconds(10));
}
#[test]
fn test_sleep() {
let mut c = Clock::new_fake(Utc::now());
let c2 = c.split();
std::thread_spawn(|| production_with_clock(c2));
c.advance(Duration::secs(10));
}
Structs§
- Clock
- A testable source of time