I'm trying to develop some kind of time tracking CLI tool in Rust. I try to cover most of the code with unit tests and got stuck about how to use stub objects with Rusts ownership restrictions.
I have a type Tracker
which has functions to start/stop tracking time. These functions query the current system time.
To make this design testable I introduced the trait TimeService
which provides the current timestamp. I have the "real" implementation SystemTimeService
which returns the current system time and a fake implementation FakeTimeService
for tests where the time can be set from the outside.
This is the current implementation:
pub struct Tracker {
// ...
time_service: Box<TimeService>,
}
trait TimeService {
fn time(&self) -> u64;
}
Here is the test I'd like to implement:
#[test]
fn tracker_end_when_called_tracks_total_time() {
let (tracker, time_service) = init_tracker();
time_service.set_seconds(0);
tracker.start("feature1");
time_service.set_seconds(10);
tracker.end();
assert_eq!(tracker.total_time, 10);
}
fn init_tracker() -> (Tracker, &FakeTimeService) {
let time_service = Box::new(FakeTimeService::new());
let tracker = Tracker::with_time_service(time_service);
// does not compile, as time service's ownership has been
// moved to the tracker.
(tracker, &*time_service)
}
The problem is that I don't know how to access the fake time service from inside the unit test as it's ownership is taken by the tracker.
I can imagine multiple solutions:
- Use
Rc
instead ofBox
inside the tracker for shared ownership of the time service - Make the
Tracker
generic and add a type argument for the usedTimeTracker
implementation - Add lifetimes to the
init_tracker
function
I do not like using solution 1 as this would not express the idea that the service is part of the tracker (seems to violate encapsulation).
Solution 2 is probably viable but in that case I'd need to make the TimeService
trait and the used implementations public which I'd also like to avoid.
So the most promising solution without changing the design seems to be lifetimes.
Is it possible to add lifetimes to the variables in such a way that the init_tracker
function can return the tracker and the fake time service?
Are there any other solutions/best practices?