> ## 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

> Testing requirements and best practices for migration recipes

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

<Steps>
  <Step title="Create test input files">
    Add files to `tests/input/` representing code patterns your recipe should handle:

    ```javascript tests/input/file-0.js theme={null}
    const { tmpDir } = require('os');

    var t0 = tmpDir();
    let t1 = tmpDir();
    const t2 = tmpDir();
    ```

    ```javascript tests/input/file-1.mjs theme={null}
    import { tmpDir } from 'node:os';

    const temp = tmpDir();
    console.log(temp);
    ```
  </Step>

  <Step title="Create expected output files">
    Add corresponding files to `tests/expected/` with the correct transformations:

    ```javascript tests/expected/file-0.js theme={null}
    const { tmpdir } = require('os');

    var t0 = tmpdir();
    let t1 = tmpdir();
    const t2 = tmpdir();
    ```

    ```javascript tests/expected/file-1.mjs theme={null}
    import { tmpdir } from 'node:os';

    const temp = tmpdir();
    console.log(temp);
    ```

    <Note>
      File names in `input/` and `expected/` must match exactly.
    </Note>
  </Step>

  <Step title="Run the tests">
    Execute tests using the test script:

    <CodeGroup>
      ```bash Recipe-specific theme={null}
      cd recipes/my-migration
      npm test
      ```

      ```bash All recipes theme={null}
      npm test
      ```
    </CodeGroup>
  </Step>
</Steps>

## Test Coverage

Your tests should cover:

### Import/Require Variations

Test all common import patterns:

```javascript theme={null}
// 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

```javascript theme={null}
// 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:

```javascript tests/input/no-changes.js theme={null}
// File without the deprecated API
const { someOtherAPI } = require('module');

const result = someOtherAPI();
```

The expected output should be identical:

```javascript tests/expected/no-changes.js theme={null}
// 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:

```typescript src/my-helper.test.ts theme={null}
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:

```bash theme={null}
node --test src/*.test.ts
```

## Integration Testing

The default test command runs integration tests using the jssg test runner:

```json package.json theme={null}
{
  "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:

```typescript theme={null}
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:

```bash theme={null}
npm run pre-commit
```

This runs:

1. Linting with auto-fix
2. Type checking
3. All tests

### Individual Test Commands

<CodeGroup>
  ```bash Lint theme={null}
  npm run lint:fix
  ```

  ```bash Type Check theme={null}
  npm run type-check
  ```

  ```bash Tests theme={null}
  npm test
  ```
</CodeGroup>

## 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:

```yaml .github/workflows/ci.yml theme={null}
- Lint and type checking
- YAML validation
- jssg tests on all platforms
- Legacy tests on multiple Node versions
```

## Test Warnings

<Warning>
  Some integration tests modify fixture files when running the entire codemod. Remember to restore these files before committing:

  ```bash theme={null}
  git restore recipes/*/tests/
  ```
</Warning>

## Debugging Failed Tests

When a test fails:

<Steps>
  <Step title="Check the diff">
    The test runner shows differences between actual and expected output:

    ```diff theme={null}
    - const { oldAPI } = require('os');
    + const { newAPI } = require('os');
    ```
  </Step>

  <Step title="Run the transformation manually">
    Test your transformation on a single file:

    ```bash theme={null}
    npx codemod jssg run -l typescript ./src/workflow.ts ./tests/input/file-0.js
    ```
  </Step>

  <Step title="Add debug logging">
    Add console.log statements in your transformation:

    ```typescript theme={null}
    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
    }
    ```
  </Step>

  <Step title="Verify the pattern">
    Test your AST pattern in Codemod Studio:

    1. Visit [Codemod Studio](https://codemod.com/studio)
    2. Paste your code sample
    3. Test your pattern matching
  </Step>
</Steps>

## Best Practices

<AccordionGroup>
  <Accordion title="Test multiple file extensions">
    Include tests for `.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, etc.

    ```
    tests/input/
    ├── file-0.js
    ├── file-0.mjs
    ├── file-1.ts
    └── file-1.tsx
    ```
  </Accordion>

  <Accordion title="Test realistic code patterns">
    Use real-world examples from actual codebases, not just simple cases.
  </Accordion>

  <Accordion title="Test partial imports">
    Ensure transformations work when only some imports need updating:

    ```javascript theme={null}
    // Should only change oldAPI, not otherAPI
    const { oldAPI, otherAPI } = require('module');
    ```
  </Accordion>

  <Accordion title="Keep tests focused">
    Each test file should test one specific pattern or edge case.
  </Accordion>

  <Accordion title="Name tests descriptively">
    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
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<Card title="Development Workflow" icon="code-branch" href="/contributing/workflow">
  Learn about the development workflow and PR process
</Card>
