I'm using the time for e.g. ping and timeout calculation as well as animations in a game. So my (big) codebase uses std::chrono::steady_clock::now()
in many places. For testing I'd like to test those without having to resort to sleep
and friends (timeout of >=5mins will be hard to test this way...)
For this I created a custom Clock
class that uses a singleton-like instance which can be changed to provide a custom clock. A default of steady_clock
is used for production code. This Clock
can be used as a drop-in replacement of std::chrono::steady_clock
and can be mocked in test code.
I'd like a review with suggestions how to improve this or if it has any flaws. Pointers to similar/better implementations are also welcome. I'm mostly concerned about the multiple indirections (and related performance penalties) this causes: Lookup of a "constructed" flag, the impl and the vtable (3 pointers) although I only have 1 function. See godbold
#include <chrono>
#include <memory>
struct BaseClock
{
using Clock = std::chrono::steady_clock;
using time_point = Clock::time_point;
virtual ~BaseClock() = default;
virtual time_point now(){ return std::chrono::steady_clock::now(); }
};
class Clock
{
static std::unique_ptr<BaseClock>& inst(){
static std::unique_ptr<BaseClock> clock(new BaseClock);
return clock;
}
public:
using rep = BaseClock::Clock::rep;
using duration = BaseClock::Clock::duration;
using time_point = std::chrono::time_point<Clock>;
static time_point now(){ return time_point(inst()->now().time_since_epoch()); }
static void set(std::unique_ptr<BaseClock> clock){inst() = std::move(clock);}
};
int main()
{
return Clock::now().time_since_epoch().count();
}
Example usage in test code:
struct MockClock: public BaseClock{
static time_point current;
time_point now() override { return current; }
};
void test_method(){
Clock::set(new MockClock);
MockClock::current = MockClock::time_point(100);
testClass.methodUsingClock();
MockClock::current += std::chrono::seconds(10);
REQUIRE(testClass.checkTimeout());
}
BaseClock
? What's advantage against using directlystd::chrono
? Avoid nakednew
, usestd::make_unique
instead. \$\endgroup\$BaseClock
is to provide a baseline clock interface without direct dependencies. A user expecting aconst BaseClock&
or similiar can now write code independent of the underlying clock type. This allows for switching the underlying clock on a higher level without affecting the dependent code (kind of "dependency injection for clocks"). As a side effect, this allows for easier test setups (especially for weird corner cases). // I'm not a fan of the singleton inClock
, though. \$\endgroup\$BaseClock::now()
provides while defaulting tostd::chrono
. I added a usage example to make this clearer. \$\endgroup\$BaseClock&
but simply usesClock
as a drop-in replacement forstd::chrono::*clock
(I edited the question to highlight this) So yes this is exactly dependency injection but without passing it as (template or regular) parameters which would require massive changes to the codebase. As clocks are static classes a singleton is required, I don't see a way avoiding this. \$\endgroup\$