Test structure
JSSG tests support two fixture layouts:Single-file fixtures
Each test case contains oneinput.* file and one expected.* file:
- Input files must be named
input.ts(or appropriate extension) - Expected files must be named
expected.ts(or appropriate extension) - File extensions should match your target language (
.ts,.tsx,.js,.jsx)
Directory snapshot fixtures
Useinput/ and expected/ directories when your codemod creates or deletes files, or when multiple files need to be transformed together:
- Files present in both
input/andexpected/must match after the codemod runs - Files present only in
expected/are treated as created-file assertions - Files present only in
input/are treated as deleted-file assertions - Files produced by the codemod but absent from
expected/fail as unexpected extra files - Test reporting still stays per file, using names like
<fixture>_<relative-path>
Command reference
Basic syntax
Language parser to use (e.g., “tsx”, “javascript”, “typescript”).
Path to your transform file (e.g., ”./scripts/codemod.ts”).
Path to test directory (defaults to ”./tests”).
Common options
See all available options in the CLI reference.
Creating effective test cases
1. Start simple:Understanding test results
Exit codes
- 0: All tests passed
- 1: At least one test failed (diffs are printed)
- 2: Error occurred (e.g., syntax error in codemod)
Reading test output
Pro tip: Use
-v flag for detailed output when debugging test failures.Snapshot updates
--update-snapshots behaves differently depending on fixture layout:
- Single-file fixtures update the matching
expected.*file - Directory fixtures update changed files, create missing expected files, and remove stale expected files that should no longer exist
metrics.json snapshots continue to be handled separately from directory tree comparisons.
Troubleshooting
Language mismatch
Language mismatch
Problem: Tests fail with parsing errors.
Solution: Ensure the
Solution: Ensure the
-l flag matches your source files:Files not found
Files not found
Problem: “No test files found” error.
Solution: Check your directory structure and file names:
Solution: Check your directory structure and file names:
No test cases found when pointing to a subdirectory
No test cases found when pointing to a subdirectory
Problem: Running
Solution: The test directory argument must be the parent
npx codemod jssg test ... ./tests/my-case gives “No test cases found”.Solution: The test directory argument must be the parent
tests/ folder, not a specific test case subdirectory. Use --filter to run a specific case:Transform not applied
Transform not applied
Problem: Test passes but no changes were made.
Solution: This is normal if your transform returns
Solution: This is normal if your transform returns
null for unchanged files. If you expect changes:Unexpected diffs
Unexpected diffs
Problem: Test fails with unexpected output.
Solution: Update expected output if changes are intentional:For directory fixtures, this can also create new files in
Solution: Update expected output if changes are intentional:
expected/ or remove stale ones.Codemod syntax errors
Codemod syntax errors
Problem: “Syntax error in codemod” or TypeScript errors.
Solution: Check your transform file:
Solution: Check your transform file:
Workflow integration
Package script
Add to yourpackage.json:
package.json
CI/CD integration
.github/workflows/test.yml
Comparison strictness
Use the--strictness flag to control how expected and actual outputs are compared. This helps tests pass when outputs are semantically equivalent but differ in formatting.
Strictness levels
| Level | Description |
|---|---|
strict | (Default) Exact string equality. Fails on any whitespace or formatting differences. |
cst | Compare Concrete Syntax Trees. Includes all tokens but ignores whitespace content. Preserves ordering. |
ast | Compare Abstract Syntax Trees. Ignores formatting and whitespace, preserves ordering. |
loose | Loose AST comparison. Ignores formatting and ordering of unordered children (e.g., object members, imports). |
What loose comparison ignores
The loose level ignores ordering for semantically unordered constructs:
- Object property ordering:
{a: 1, b: 2}matches{b: 2, a: 1} - Import ordering:
import {a, b} from 'x'matchesimport {b, a} from 'x' - Keyword argument ordering (Python):
func(a=1, b=2)matchesfunc(b=2, a=1) - Interface member ordering: Order of properties in TypeScript interfaces
- Derive attribute ordering (Rust):
#[derive(Debug, Clone)]matches#[derive(Clone, Debug)]
When to use each level
strict: Default. Use when exact output matters (most cases).cst: Use when you want to compare token structure but ignore whitespace content.ast: Use when you want to ignore formatting differences but preserve ordering.loose: Use when your codemod modifies structures where order doesn’t matter semantically.
Supported languages
| Language | Features |
|---|---|
| JavaScript/JSX | Objects, imports, exports |
| TypeScript/TSX | Objects, imports, exports, type definitions, interfaces |
| Python | Dictionaries, imports, keyword arguments, type unions |
| Go | Imports, struct literals (keyed), interface definitions |
| Rust | Struct expressions, derive attributes, use lists, trait bounds |
| JSON | Objects |
Best practices
- Start simple: Begin with basic transformations before complex ones
- Test edge cases: Include empty files, files with comments, and malformed code
- Use descriptive names: Make test cases self-documenting
- Keep tests focused: One transformation per test case
- Validate locally: Always test before publishing
- Use
--strictness loose: When testing transforms that may produce semantically equivalent outputs with different formatting or ordering