Skip to main content
Robot software is notoriously hard to test. Real hardware behaves differently each run, timing varies with system load, and a test that passes on Tuesday may fail on Wednesday for no obvious reason. These problems compound in CI environments where you cannot attach a physical robot. Basis was designed with testability as its primary goal. The framework’s core execution model — Inputs → Synchronizer → Handler → Outputs — makes it possible to feed a Unit exactly the inputs you want and observe exactly the outputs it produces, without a running robot.

The problem with non-deterministic robot tests

A typical robotics callback-based system couples your logic directly to a live transport. To test the logic you must:
  • Stand up the full transport stack
  • Time message delivery correctly
  • Accept that the system clock, thread scheduling, and network conditions all affect results
The same test can produce different outputs depending on when messages arrive relative to each other and relative to wall-clock timers. This makes flaky tests the norm rather than the exception.

How Basis enables deterministic testing

Basis separates your logic from the transport. Every handler in a Unit follows the same contract:
Inputs (typed messages) + Synchronizer (firing condition) → Handler (your code) → Outputs (typed messages)
The HandlerPubSub structs generated for each handler expose a type_erased_callbacks map. Each entry maps a topic name to a callback that can receive a pre-deserialized message and, when the synchronizer fires, returns a TopicMap of output messages — all without touching the live transport. The Unit base class grants DeterministicReplayer friendship explicitly so it can drive handlers directly:
// From unit.h
class Unit {
  // ...
  friend class basis::DeterministicReplayer;
  friend class basis::UnitManager;
  std::map<std::string, HandlerPubSub *> handlers;
  // ...
};
This means DeterministicReplayer can inject recorded messages into a Unit’s handler map in the exact order and at the exact timestamps they were originally observed, producing identical outputs every time.

The two testing approaches

Unit testing

Instantiate a Unit directly in a test binary, call Update() by hand, and publish messages manually. No coordinator or transport daemon needed.

Deterministic replay

Record a real robot run to an MCAP file, then replay it in CI. The DeterministicReplayer feeds messages back through each Unit’s handler map and produces identical outputs on every run.

Unit testing

Because Unit::Update() is a plain virtual method, you can instantiate any Unit subclass in a test binary and drive it without a live coordinator:
1

Instantiate the Unit

Construct your Unit and call CreateTransportManager(). You can pass a RecorderInterface* if you want to capture output messages.
MyUnit unit;
unit.CreateTransportManager();
2

Initialize without subscribers

Pass UnitInitializeOptions{.create_subscribers = false} to skip creating real transport subscribers. The handler map is still populated, which is what you need for injecting messages.
unit.Initialize({.create_subscribers = false});
3

Publish test messages and call Update()

Advertise test publishers and publish messages directly, then call Update() to drain the callback queue.
auto pub = unit.Advertise<MySensorMsg>("/sensor");
auto msg = std::make_shared<MySensorMsg>();
msg->set_value(42.0f);
pub->Publish(msg);

unit.Update(nullptr, basis::core::Duration::FromSecondsNanoseconds(0, 0));
4

Assert on outputs

Subscribe to the Unit’s output topics or inspect state directly to verify the result.
Unit testing as described above works today without any premium features. Full deterministic replay — where a DeterministicReplayer drives handlers from a recorded MCAP file in CI — is a premium feature. The open-source Replayer class (which publishes messages back through the transport layer) is available to all users.

Next steps

Recording

Record topics to MCAP files using the Recorder and AsyncRecorder classes

Deterministic replay

Replay recordings through the Replayer or DeterministicReplayer to reproduce robot runs in CI

Build docs developers (and LLMs) love