Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nodejs/userland-migrations/llms.txt

Use this file to discover all available pages before exploring further.

Testing is critical for ensuring migration recipes work correctly across different code patterns and edge cases.

Test Structure

Each recipe includes a tests/ directory with two subdirectories:
recipes/my-migration/
└── tests/
    ├── input/       # Test input files (before transformation)
    └── expected/    # Expected output files (after transformation)

Writing Tests

1

Create test input files

Add files to tests/input/ representing code patterns your recipe should handle:
tests/input/file-0.js
const { tmpDir } = require('os');

var t0 = tmpDir();
let t1 = tmpDir();
const t2 = tmpDir();
tests/input/file-1.mjs
import { tmpDir } from 'node:os';

const temp = tmpDir();
console.log(temp);
2

Create expected output files

Add corresponding files to tests/expected/ with the correct transformations:
tests/expected/file-0.js
const { tmpdir } = require('os');

var t0 = tmpdir();
let t1 = tmpdir();
const t2 = tmpdir();
tests/expected/file-1.mjs
import { tmpdir } from 'node:os';

const temp = tmpdir();
console.log(temp);
File names in input/ and expected/ must match exactly.
3

Run the tests

Execute tests using the test script:
cd recipes/my-migration
npm test

Test Coverage

Your tests should cover:

Import/Require Variations

Test all common import patterns:
// CommonJS require
const { api } = require('module');
const api = require('module').api;
const mod = require('node:module');

// ES6 import
import { api } from 'module';
import { api } from 'node:module';
import * as mod from 'module';

Edge Cases

// Renamed imports
import { oldAPI as custom } from 'module';
const { oldAPI: renamed } = require('module');

// Multiple imports
import { api1, api2, oldAPI } from 'module';

// Destructured usage
const result = oldAPI().property;

// Method chaining
oldAPI().method().another();

No-op Cases

Test that your recipe doesn’t modify files that don’t need changes:
tests/input/no-changes.js
// File without the deprecated API
const { someOtherAPI } = require('module');

const result = someOtherAPI();
The expected output should be identical:
tests/expected/no-changes.js
// File without the deprecated API
const { someOtherAPI } = require('module');

const result = someOtherAPI();

Unit Testing (Advanced)

For complex recipes with helper functions, create unit tests alongside your code:
src/my-helper.test.ts
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { myHelper } from './my-helper.ts';

describe('myHelper', () => {
  it('should transform pattern correctly', () => {
    const input = 'oldAPI()';
    const expected = 'newAPI()';
    assert.equal(myHelper(input), expected);
  });

  it('should handle edge case', () => {
    const input = 'oldAPI.method()';
    const expected = 'newAPI.method()';
    assert.equal(myHelper(input), expected);
  });
});
Run unit tests:
node --test src/*.test.ts

Integration Testing

The default test command runs integration tests using the jssg test runner:
package.json
{
  "scripts": {
    "test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
  }
}
This command:
  1. Loads your transformation from src/workflow.ts
  2. Applies it to files in tests/input/
  3. Compares results against tests/expected/
  4. Reports any differences

Test Fixtures

When testing file system operations or complex scenarios, use fixtures:
recipes/correct-ts-specifiers/
└── src/
    ├── fixtures/
    │   └── e2e/
    │       ├── test.js
    │       └── module.ts
    └── workflow.test.ts
Reference fixtures in tests:
import { fileURLToPath } from 'node:url';

const fixtureDir = fileURLToPath(
  import.meta.resolve('./fixtures/e2e/')
);

Running Tests Locally

Before Committing

Always run the full test suite before committing:
npm run pre-commit
This runs:
  1. Linting with auto-fix
  2. Type checking
  3. All tests

Individual Test Commands

npm run lint:fix

CI/CD Testing

All tests run automatically in GitHub Actions on:
  • Every push to any branch
  • Every pull request
  • Multiple Node.js versions (22+)
  • Multiple operating systems (Ubuntu, macOS, Windows)
The CI workflow runs:
.github/workflows/ci.yml
- Lint and type checking
- YAML validation
- jssg tests on all platforms
- Legacy tests on multiple Node versions

Test Warnings

Some integration tests modify fixture files when running the entire codemod. Remember to restore these files before committing:
git restore recipes/*/tests/

Debugging Failed Tests

When a test fails:
1

Check the diff

The test runner shows differences between actual and expected output:
- const { oldAPI } = require('os');
+ const { newAPI } = require('os');
2

Run the transformation manually

Test your transformation on a single file:
npx codemod jssg run -l typescript ./src/workflow.ts ./tests/input/file-0.js
3

Add debug logging

Add console.log statements in your transformation:
export default function transform(root: SgRoot<Js>): string | null {
  const rootNode = root.root();
  console.log('Processing file...');
  
  const matches = rootNode.findAll({ rule: { pattern: 'oldAPI' } });
  console.log(`Found ${matches.length} matches`);
  
  // ... rest of transformation
}
4

Verify the pattern

Test your AST pattern in Codemod Studio:
  1. Visit Codemod Studio
  2. Paste your code sample
  3. Test your pattern matching

Best Practices

Include tests for .js, .mjs, .cjs, .ts, .tsx, etc.
tests/input/
├── file-0.js
├── file-0.mjs
├── file-1.ts
└── file-1.tsx
Use real-world examples from actual codebases, not just simple cases.
Ensure transformations work when only some imports need updating:
// Should only change oldAPI, not otherAPI
const { oldAPI, otherAPI } = require('module');
Each test file should test one specific pattern or edge case.
Use clear names that indicate what’s being tested:
file-0.js      → basic-require.js
file-1.mjs     → esm-import.mjs
file-2.ts      → renamed-import.ts

Next Steps

Development Workflow

Learn about the development workflow and PR process

Build docs developers (and LLMs) love