Skip to main content
Test your JSSG codemod using snapshot testing with single-file fixtures or full directory snapshots.

Test structure

JSSG tests support two fixture layouts:

Single-file fixtures

Each test case contains one input.* file and one expected.* file:
File naming:
  • 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

Use input/ and expected/ directories when your codemod creates or deletes files, or when multiple files need to be transformed together:
Directory fixtures are compared as snapshots of the whole tree:
  • Files present in both input/ and expected/ 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>
The test directory must be the parent folder that contains your test case subdirectories (e.g., ./tests). Passing a specific test case directory like ./tests/basic-transform will result in a “No test cases found” error. To run a specific test case, use the --filter flag instead.

Command reference

Basic syntax

-l, --language
string
required
Language parser to use (e.g., “tsx”, “javascript”, “typescript”).
codemod-file
string
required
Path to your transform file (e.g., ”./scripts/codemod.ts”).
test-directory
string
Path to test directory (defaults to ”./tests”).
Common parser-backed config formats are supported directly:

Common options

See all available options in the CLI reference.

Creating effective test cases

1. Start simple:
2. Add edge cases:
3. Test different file types:

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

Problem: Tests fail with parsing errors.

Solution: Ensure the -l flag matches your source files:
Problem: “No test files found” error.

Solution: Check your directory structure and file names:
Problem: Running 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:
Problem: Test passes but no changes were made.

Solution: This is normal if your transform returns null for unchanged files. If you expect changes:
Problem: Test fails with unexpected output.

Solution: Update expected output if changes are intentional:
For directory fixtures, this can also create new files in expected/ or remove stale ones.
Problem: “Syntax error in codemod” or TypeScript errors.

Solution: Check your transform file:

Workflow integration

Package script

Add to your package.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

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' matches import {b, a} from 'x'
  • Keyword argument ordering (Python): func(a=1, b=2) matches func(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

AST comparison preserves order when it matters semantically. For example, spread operators ({...x, a: 1} vs {a: 1, ...x}) maintain their order because they affect property override behavior.

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