This guide explains how to write effective unit tests for code generators in Atomic EHR Codegen. Testing generators is critical to ensure that generated code is correct, maintainable, and consistent across language targets.
Table of Contents
Generator tests validate that the code writers produce correct output without side effects. The testing strategy uses:
- In-memory generation via
APIBuilderwithinMemoryOnly: trueto avoid file I/O - Snapshot testing to capture and verify exact output
- File count assertions to catch missing or unexpected output
- Structured test organization mirroring language and generation types
Generator tests are located in test/api/ and organized by generation method:
test/api/
├── write-generator/ # Tests for language-specific writers
│ ├── __snapshots__/ # Snapshot files
│ ├── typescript.test.ts # TypeScript writer tests
│ ├── python.test.ts # Python writer tests
│ ├── csharp.test.ts # C# writer tests
│ └── [language].test.ts # Additional language tests
└── mustache.test.ts # Mustache template generator tests
Each test file targets a specific generator and validates its output independently.
All generator tests follow this basic structure:
import { describe, expect, it } from "bun:test";
import { APIBuilder } from "@root/api/builder";
import { silentLogger, r4Manager } from "@typeschema-test/utils";
describe("TypeScript Writer Generator", async () => {
const result = await new APIBuilder({ manager: r4Manager, logger: silentLogger })
.typescript({
inMemoryOnly: true,
})
.generate();
expect(result.success).toBeTrue();
const files = result.filesGenerated.typescript!;
expect(Object.keys(files).length).toEqual(236);
it("generates Patient resource with snapshot", async () => {
expect(files["generated/types/hl7-fhir-r4-core/Patient.ts"])
.toMatchSnapshot();
});
});APIBuilder Setup:
- Initialize with test manager and silent logger:
new APIBuilder({ manager: r4Manager, logger: silentLogger }) - Choose generator method:
.typescript(),.python(),.csharp(),.mustache() - Enable in-memory mode:
inMemoryOnly: true(no file I/O)
Generation Result:
- Contains
successboolean flag - Contains
filesGeneratednested by generator name, then by path:filesGenerated[generator][path] = content - Can be accessed and asserted in tests
Assertions:
- Validate success:
expect(result.success).toBeTrue() - Check file count for a generator:
expect(Object.keys(result.filesGenerated.typescript!).length).toEqual(expected) - Snapshot specific files:
expect(result.filesGenerated.typescript![path]).toMatchSnapshot()
- shouldRunHooks: Set to
falsein tests to skip post-generation scripts - inMemoryOnly: Always use to avoid file I/O
- meta.timestamp: Mock timestamp for reproducible output for Mustache generators
- throwException(): Call to fail tests on generation errors
- debug mode: Set to capture generated models in output (useful for debugging)
Snapshots capture the exact generated output for comparison across test runs. They're stored in test/api/write-generator/__snapshots__/ as plain text files.
When you run tests for the first time, Bun creates snapshot files automatically:
bun test test/api/write-generator/typescript.test.tsSnapshots are stored in files like:
test/api/write-generator/__snapshots__/typescript.test.snap
After intentional changes to code generation logic, update snapshots:
bun test -- --update-snapshotsThis updates all snapshot files to match current output.
-
Review changes carefully - Always review snapshot diffs before committing:
git diff test/api/write-generator/__snapshots__/
-
Use meaningful commits - When updating snapshots, include reason in commit:
git commit -m "test(typescript): update snapshots for camelCase field names" -
One change at a time - Update snapshots for one feature at a time, not multiple changes together
-
Document intent - Add comments in test code explaining why output changed:
it("generates fields with camelCase names", async () => { // Changed from PascalCase to camelCase to match TypeScript conventions expect(result.filesGenerated.typescript!["generated/types/Patient.ts"]) .toMatchSnapshot(); });
To run all generator tests, use:
bun test test/api/For testing specific generators, you can target individual test files or directories. To test only the TypeScript generator:
bun test test/api/write-generator/typescript.test.tsTo test only the Python generator:
bun test test/api/write-generator/python.test.tsTo run all write-generator tests together:
bun test test/api/write-generator/You can also run tests with coverage reporting to see how much of your code is covered by tests:
bun test --coverage test/api/For development, watch mode automatically reruns tests when files change:
bun test --watch test/api/write-generator/