Skip to content

@codemod-utils/tests

Utilities for tests

What is it?

@codemod-utils/tests helps you write tests for codemods.

API

assert

The assert object comes from Node.js.

ts
import { assert, test } from '@codemod-utils/tests';

import { createOptions } from '../../../src/steps/index.js';
import {
  codemodOptions,
  options,
} from '../../helpers/shared-test-setups/sample-project.js';

test('steps | create-options > sample-project', function () {
  assert.deepStrictEqual(createOptions(codemodOptions), options);
});

Make strong assertions whenever possible, by using assert.deepStrictEqual, assert.strictEqual, and assert.throws. Avoid weak assertions like assert.match and assert.ok, which create a "room for interpretation" and can make tests pass when they shouldn't (false negatives).

  • assert.deepStrictEqual checks "complex" data structures (array, object, Map).
  • assert.strictEqual checks "simple" data structures (Boolean, number, string).
  • assert.throws checks error messages.

assertFixture

Asserts that the codemod updated the input project correctly.

Checks that all file names and contents specified in the outputProject (expected) match those in the updated input project (actual).

ts
/**
 * @param outputProject
 *
 * The folders and files that we expect to see, represented as a
 * JSON (possibly nested).
 *
 * The object keys are the folder and file names. The object values
 * are either a JSON (in the case of a folder key) or a string that
 * stores the file content (in the case of a file key).
 *
 * @param options
 *
 * An object with `projectRoot`. Here, `projectRoot` denotes where
 * we created the fixture for a test. (This is somewhere in the `tmp`
 * folder.)
 *
 * @return
 *
 * Returns `true`, if and only if, all folders and files are present
 * and all file contents are correct.
 */
function assertFixture(outputProject: DirJSON, options: Options): void;
ts
import { assertFixture, loadFixture, test } from '@codemod-utils/tests';

import { runCodemod } from '../../src/index.js';
import {
  inputProject,
  outputProject,
} from '../fixtures/sample-project/index.js';
import { codemodOptions } from '../helpers/shared-test-setups/sample-project.js';

test('index > sample-project', function () {
  loadFixture(inputProject, codemodOptions);

  runCodemod(codemodOptions);

  assertFixture(outputProject, codemodOptions);

  // Check idempotence
  runCodemod(codemodOptions);

  assertFixture(outputProject, codemodOptions);
});

convertFixtureToJson

Reads the fixture (folders and files) at the specified path. Returns a JSON representation for fixture-driven tests.

ts
/**
 * @param projectRoot
 *
 * Where the fixture can be found, relative to the `tests/fixtures`
 * folder in the codemod project.
 *
 * @return
 *
 * A JSON, which can then be passed to `loadFixture()` or
 * `assertFixture()`.
 */
function convertFixtureToJson(projectRoot: string): DirJSON;
ts
import { convertFixtureToJson } from '@codemod-utils/tests';

const inputProject = convertFixtureToJson('sample-project/input');
const outputProject = convertFixtureToJson('sample-project/output');

export { inputProject, outputProject };

loadFixture

Creates a fixture (folders and files) at the specified path.

ts
/**
 * @param inputProject
 *
 * The folders and files that we want to create, represented as a
 * JSON (possibly nested).
 *
 * The object keys are the folder and file names. The object values
 * are either a JSON (in the case of a folder key) or a string that
 * stores the file content (in the case of a file key).
 *
 * @param options
 */
function loadFixture(inputProject: DirJSON, options: Options): void;
ts
import { assertFixture, loadFixture, test } from '@codemod-utils/tests';

import { runCodemod } from '../../src/index.js';
import {
  inputProject,
  outputProject,
} from '../fixtures/sample-project/index.js';
import { codemodOptions } from '../helpers/shared-test-setups/sample-project.js';

test('index > sample-project', function () {
  loadFixture(inputProject, codemodOptions);

  runCodemod(codemodOptions);

  assertFixture(outputProject, codemodOptions);

  // Check idempotence
  runCodemod(codemodOptions);

  assertFixture(outputProject, codemodOptions);
});

TIP

The example above showed an acceptance test, where inputProject and outputProject are derived from files that exist (somewhere in /tests/fixtures).

For integration and unit tests, it may be better to bypass convertFixtureToJson and define inputProject and outputProject directly in the test file.

ts
test('steps | add-license > base case', function () {
  const inputProject = {};

  const outputProject = {
    'LICENSE.md': 'The MIT License (MIT)',
  };

  loadFixture(inputProject, codemodOptions);

  addLicense(options);

  assertFixture(outputProject, codemodOptions);
});

normalizeFile

Creates a file (its content) with the correct newline character, so that a test can pass on both POSIX and Windows.

ts
/**
 * @param lines
 *
 * An array of texts.
 *
 * @return
 *
 * A string that represents the file content.
 */
function normalizeFile(lines: string[]): string;
ts
/**
 * Assert that `transform` correctly updates a file.
 */
const oldFile = normalizeFile([
  `import Component from '@glimmer/component';`,
  ``,
  `export default class Hello extends Component {}`,
  ``,
]);

const newFile = transform(oldFile);

assert.strictEqual(
  newFile,
  normalizeFile([
    `import Component from '@glimmer/component';`,
    ``,
    `import styles from './hello.module.css';`,
    ``,
    `export default class Hello extends Component {`,
    `  styles = styles;`,
    `}`,
    ``,
  ]),
);

test

The test method comes from @sondr3/minitest.

ts
import { test } from '@codemod-utils/tests';

test('index > sample-project', function () {
  // ...
});

IMPORTANT

A test file must have the extension .test.ts or .test.js.

You can append .ignore() or .only() to run a subset of tests. This may be useful for developing a feature or debugging an issue.

ts
test('index > sample-project', function () {
  // ...
}).ignore();
ts
test('index > sample-project', function () {
  // ...
}).only();