Testing with Clock Mocks#
Overview#
Both test utilities work with any clock domain. Choose based on how the SUT obtains the clock:
Utility |
When to use |
|---|---|
|
SUT calls |
|
SUT accepts |
T1 — ScopedClockOverride (Scope-Bound Override)#
Use when the system under test calls Clock<Tag>::GetInstance() internally. The guard
installs a mock backend into the process-wide singleton for the duration of its scope,
then restores the original on destruction.
Warning
ScopedClockOverride modifies a process-wide singleton. Any cc_test target that
uses it must declare tags = ["exclusive", "unit"] in its Bazel BUILD file. Without
"exclusive", Bazel may run multiple tests in the same process shard in parallel, causing
one test’s mock to corrupt another test’s clock state and producing flaky failures.
cc_test(
name = "my_service_test",
srcs = ["my_service_test.cpp"],
tags = ["exclusive", "unit"],
deps = [...],
)
If the SUT receives the clock via constructor injection instead, use
ClockTestFactory (T2) — it does not touch the global singleton and
requires no special tag.
Single clock domain:
#include "score/time/vehicle_time/src/vehicle_clock.h"
#include "score/time/vehicle_time/src/vehicle_clock_backend_mock.h"
TEST(MyServiceTest, ReportsReliableTime)
{
auto mock = std::make_shared<score::time::VehicleClockBackendMock>();
EXPECT_CALL(*mock, Now()).WillOnce(Return(/* snapshot */));
const score::time::test_utils::ScopedClockOverride<score::time::VehicleTime> guard{mock};
MyService svc;
svc.DoSomething(); // calls VehicleClock::GetInstance() internally
}
Multiple clock domains (as used in the vehicle_time example):
#include "score/time/clock/src/scoped_clock_override.h"
#include "score/time/vehicle_time/src/vehicle_clock_backend_mock.h"
#include "score/time/high_res_steady_time/src/high_res_steady_clock_backend_mock.h"
class VehicleTimeHandlerTest : public ::testing::Test
{
protected:
VehicleTimeHandlerTest()
: vehicle_mock_{std::make_shared<score::time::VehicleClockBackendMock>()},
hirs_mock_{std::make_shared<score::time::HighResSteadyClockBackendMock>()},
vehicle_guard_{vehicle_mock_},
hirs_guard_{hirs_mock_}
{
}
std::shared_ptr<score::time::VehicleClockBackendMock> vehicle_mock_;
std::shared_ptr<score::time::HighResSteadyClockBackendMock> hirs_mock_;
score::time::test_utils::ScopedClockOverride<score::time::VehicleTime> vehicle_guard_;
score::time::test_utils::ScopedClockOverride<score::time::HighResSteadyTime> hirs_guard_;
};
TEST_F(VehicleTimeHandlerTest, ReportContainsSynchronizedVehicleTime)
{
EXPECT_CALL(*vehicle_mock_, Now()).WillOnce(Return(/* vehicle_snapshot */));
EXPECT_CALL(*hirs_mock_, Now()).WillOnce(Return(/* hirs_snapshot */));
VehicleTimeHandler handler;
const auto report = handler.GetCurrentTime();
EXPECT_TRUE(report.is_reliable);
}
The full working example is in
examples/time/vehicle_time/src/vehicle_time_handler_test.cpp.
T2 — ClockTestFactory (Constructor Injection)#
Use when the SUT accepts Clock<Tag> as a constructor argument. No global singleton
is touched — safe to run in parallel without "exclusive" tag.
#include "score/time/vehicle_time/src/vehicle_clock.h"
#include "score/time/vehicle_time/src/vehicle_clock_backend_mock.h"
TEST(MyServiceTest, ReportsReliableTime)
{
auto mock = std::make_shared<score::time::VehicleClockBackendMock>();
EXPECT_CALL(*mock, Now()).WillOnce(Return(/* snapshot */));
const auto clock =
score::time::test_utils::ClockTestFactory<score::time::VehicleTime>::Make(mock);
MyService svc{clock};
svc.DoSomething();
}
Bazel Dependencies#
Choose the target that matches your use case:
Target |
When to use |
|---|---|
|
Production binary — includes real PTP backend |
|
Unit test — |
|
Test utilities — |
|
Header-only, no backend — interface/type usage only; required when subscribing
to |