Skip to main content
Test your JSSG codemod using snapshot testing with input/expected file pairs.

Test structure

JSSG tests use a simple directory structure where each test case contains two files:
tests/
  basic-transform/          # Test case directory
    input.ts               # Code before transformation
    expected.ts            # Expected output after transformation
  edge-cases/               # Another test case
    input.tsx
    expected.tsx
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)

Command reference

Basic syntax

npx codemod jssg test -l <language> <codemod-file> [test-directory]
-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 options

# Update expected outputs when changes are intentional
npx codemod jssg test -l tsx ./scripts/codemod.ts -u

# Run only tests matching a pattern
npx codemod jssg test -l tsx ./scripts/codemod.ts --filter "edge-cases"

# Show detailed output for debugging
npx codemod jssg test -l tsx ./scripts/codemod.ts -v

# Run tests one at a time (helpful for debugging)
npx codemod jssg test -l tsx ./scripts/codemod.ts --sequential

# Use loose comparison (ignores formatting and property ordering)
npx codemod jssg test -l tsx ./scripts/codemod.ts --strictness loose
See all available options in the CLI reference.

Creating effective test cases

1. Start simple:
// input.ts
console.log("test");

// expected.ts  
logger.log("test");
2. Add edge cases:
// input.ts
// Should not transform
const preserved = "unchanged";

// Should transform
console.log("transform me");
3. Test different file types:
// input.tsx
function Component() {
  console.log("Hello");
  return <div>World</div>;
}

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

 tests/basic-transform
 tests/edge-cases
  Expected: logger.log("test")
  Actual:   console.log("test")
Pro tip: Use -v flag for detailed output when debugging test failures.

Troubleshooting

Problem: Tests fail with parsing errors.

Solution: Ensure the -l flag matches your source files:
# For TypeScript files
npx codemod jssg test -l typescript ./scripts/codemod.ts

# For TSX files  
npx codemod jssg test -l tsx ./scripts/codemod.ts

# For JavaScript files
npx codemod jssg test -l javascript ./scripts/codemod.ts
Problem: “No test files found” error.

Solution: Check your directory structure and file names:
# Verify structure
ls -la tests/basic-transform/
# Should show: input.ts  expected.ts

# Check file names are correct
ls tests/basic-transform/
# Should show: input.ts  expected.ts
Problem: Test passes but no changes were made.

Solution: This is normal if your transform returns null for unchanged files. If you expect changes:
# Run with verbose output to debug
npx codemod jssg test -l tsx ./scripts/codemod.ts -v
Problem: Test fails with unexpected output.

Solution: Update expected output if changes are intentional:
# Update snapshots
npx codemod jssg test -l tsx ./scripts/codemod.ts -u
Problem: “Syntax error in codemod” or TypeScript errors.

Solution: Check your transform file:
# Validate TypeScript
npx tsc --noEmit ./scripts/codemod.ts

# Test with a simple file first
echo 'console.log("test");' > test-input.js
npx codemod jssg run -l javascript ./scripts/codemod.ts test-input.js

Workflow integration

Package script

Add to your package.json:
package.json
{
  "scripts": {
    "test": "npx codemod jssg test -l tsx ./scripts/codemod.ts",
    "test:update": "npx codemod jssg test -l tsx ./scripts/codemod.ts -u",
    "test:verbose": "npx codemod jssg test -l tsx ./scripts/codemod.ts -v"
  }
}

CI/CD integration

.github/workflows/test.yml
name: Test Codemod
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '24' # Use current LTS
      - run: npm test

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.
npx codemod jssg test -l tsx ./scripts/codemod.ts --strictness <LEVEL>

Strictness levels

LevelDescription
strict(Default) Exact string equality. Fails on any whitespace or formatting differences.
cstCompare Concrete Syntax Trees. Includes all tokens but ignores whitespace content. Preserves ordering.
astCompare Abstract Syntax Trees. Ignores formatting and whitespace, preserves ordering.
looseLoose 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' 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

LanguageFeatures
JavaScript/JSXObjects, imports, exports
TypeScript/TSXObjects, imports, exports, type definitions, interfaces
PythonDictionaries, imports, keyword arguments, type unions
GoImports, struct literals (keyed), interface definitions
RustStruct expressions, derive attributes, use lists, trait bounds
JSONObjects
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