Unit testing patterns, mock builder usage, and test data construction strategies for the Lichtblick monorepo.
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.github/skills/unit-testing/SKILL.mdThe project follows ADR-0002: Reusable Mock Builders for Testing — a Builder pattern that centralizes test data creation across unit, integration, and E2E tests.
@lichtblick/test-builders (external published npm dependency, not a workspace package)
├── BasicBuilder (primitives: string, number, boolean, date, lists, etc.)
└── defaults<T>(overrides, defaults) (utility to merge partial overrides with defaults)
@lichtblick/suite-base/testing/builders/ (domain builders)
├── PlayerBuilder.ts (PlayerState, Topic, TopicStats, ActiveData)
├── MessageEventBuilder.ts (MessageEvent with topic + data)
├── RosTimeBuilder.ts (Time { sec, nsec })
├── RosDatatypesBuilder.ts (MessageDefinition, datatypes maps)
├── LayoutBuilder.ts (Layout configs, global variables)
├── RenderStateBuilder.ts (Panel renderState)
├── ExtensionBuilder.ts (Extension metadata)
├── PlotBuilder.ts (Plot panel data)
├── DiagnosticsBuilder.ts (DiagnosticStatusArray)
├── SettingsTreeNodeBuilder.ts (Settings tree nodes)
├── InitializationSourceBuilder.ts (Data source initialization)
└── ... (more domain-specific builders)import { BasicBuilder } from "@lichtblick/test-builders";
import PlayerBuilder from "@lichtblick/suite-base/testing/builders/PlayerBuilder";
// Create with all defaults — valid data without specifying anything
const topic = PlayerBuilder.topic();
// Override only what matters for this test
const customTopic = PlayerBuilder.topic({ name: "/camera/image" });
// Generate multiple instances
const topics = PlayerBuilder.topics(5);
// Compose builders for complex objects
const state = PlayerBuilder.playerState({
activeData: PlayerBuilder.activeData({
topics: [PlayerBuilder.topic({ name: "/lidar" })],
currentTime: RosTimeBuilder.time({ sec: 100, nsec: 0 }),
}),
});Follow the existing pattern when adding builders for new domain types:
import { BasicBuilder, defaults } from "@lichtblick/test-builders";
class MyEntityBuilder {
// Static factory method with partial overrides
public static myEntity(props: Partial<MyEntity> = {}): MyEntity {
return defaults<MyEntity>(props, {
id: BasicBuilder.string(),
name: BasicBuilder.string(),
count: BasicBuilder.number(),
enabled: BasicBuilder.boolean(),
createdAt: BasicBuilder.date(),
});
}
// Plural helper for generating lists
public static myEntities(count = 3): MyEntity[] {
return BasicBuilder.multiple(MyEntityBuilder.myEntity, count);
}
}
export default MyEntityBuilder;defaults<T>() helper — merges partial overrides with generated defaultsPlayerBuilder calls RosTimeBuilder)@lichtblick/test-builders (BasicBuilder)@lichtblick/suite-base/testing/builders/builders/ directory within the component folderThe BasicBuilder class (from @lichtblick/test-builders) provides:
| Method | Returns |
|---|---|
BasicBuilder.string() | Random string |
BasicBuilder.number() | Random number |
BasicBuilder.boolean() | Random boolean |
BasicBuilder.date() | Random Date |
BasicBuilder.strings(count?) | Array of strings |
BasicBuilder.sample(array, count?) | Random sample from array |
BasicBuilder.multiple(factory, count?) | Array of N items from factory function |
BasicBuilder.genericMap(valueFn) | Map with generated keys/values |
BasicBuilder.genericDictionary(valueFn) | Record<string, T> with generated entries |
PlayerBuilder.topic() instead of { name: "/foo", schemaName: "bar" }See the Unit Test Agent (.github/agents/lb-unit-test.agent.md) for:
lichtblick-suite/lichtblick
9589f38
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.