BUPKIS
    Preparing search index...

    @bupkis/from-jest

    Migrate Jest and Vitest assertions to bupkis with a single command.

    npx @bupkis/from-jest
    

    Or install globally:

    npm install -g @bupkis/from-jest
    bupkis-from-jest
    # Transform all test files in current directory
    npx @bupkis/from-jest

    # Transform specific patterns
    npx @bupkis/from-jest "src/**/*.test.ts" "tests/**/*.spec.ts"

    # Dry run (see what would change)
    npx @bupkis/from-jest --dry-run

    # Strict mode (fail on any unsupported matcher)
    npx @bupkis/from-jest --strict

    # Exclude patterns
    npx @bupkis/from-jest -e "**/fixtures/**" -e "**/snapshots/**"

    # Transform mock/spy matchers using @bupkis/sinon
    npx @bupkis/from-jest --sinon
    • --best-effort (default): Transform what we can, add // TODO: Manual migration needed comments for complex cases
    • --strict: Fail immediately on any unsupported transformation
    • --interactive: Prompt for ambiguous cases (coming soon)

    Supported versions:

    • Jest 29+ (including Jest 30)
    • Vitest 1+

    The codemod handles imports from:

    • Jest: @jest/globals
    • Vitest: vitest
    • Global expect: Adds import { expect } from 'bupkis'

    When transforming, the codemod:

    1. Removes expect from your test framework import
    2. Adds import { expect } from 'bupkis'
    3. Keeps other imports (describe, it, test, etc.) from your original framework

    Note: Jest 30 removed several matcher aliases (e.g., toThrowErrortoThrow, toBeCalledtoHaveBeenCalled). This codemod handles both the old aliases and the new canonical names.

    Jest bupkis
    expect(x).toBe(y) expect(x, 'to be', y)
    expect(x).toEqual(y) expect(x, 'to deep equal', y)
    expect(x).toBeTruthy() expect(x, 'to be truthy')
    expect(x).toBeFalsy() expect(x, 'to be falsy')
    expect(x).toBeNull() expect(x, 'to be null')
    expect(x).toBeUndefined() expect(x, 'to be undefined')
    expect(x).toBeDefined() expect(x, 'to be defined')
    expect(x).toBeInstanceOf(Y) expect(x, 'to be an instance of', Y)
    expect(x).toBeGreaterThan(y) expect(x, 'to be greater than', y)
    expect(x).toBeLessThan(y) expect(x, 'to be less than', y)
    expect(x).toMatch(pattern) expect(x, 'to match', pattern)
    expect(x).toContain(y) expect(x, 'to contain', y)
    expect(x).toHaveLength(n) expect(x, 'to have length', n)
    expect(x).toHaveProperty(k) expect(x, 'to have property', k)
    expect(x).toMatchObject(y) expect(x, 'to satisfy', y)
    expect(fn).toThrow() expect(fn, 'to throw')
    jest-extended bupkis
    expect(x).toBeTrue() expect(x, 'to be true')
    expect(x).toBeFalse() expect(x, 'to be false')
    expect(x).toBeArray() expect(x, 'to be an array')
    expect(x).toBeEmpty() expect(x, 'to be empty')
    expect(x).toStartWith(s) expect(x, 'to start with', s)
    expect(x).toEndWith(s) expect(x, 'to end with', s)
    expect(x).toBeOneOf(arr) expect(x, 'to be one of', arr)

    All matchers support negation:

    // Jest/Vitest
    expect(x).not.toBe(y);

    // bupkis
    expect(x, 'not to be', y);

    When you use the --sinon flag, the codemod transforms Jest mock matchers to @bupkis/sinon assertions:

    Jest (canonical) Jest 29 alias @bupkis/sinon
    toHaveBeenCalled() toBeCalled() 'was called'
    toHaveBeenCalledTimes(n) toBeCalledTimes() 'was called times', n
    toHaveBeenCalledWith(...) toBeCalledWith() 'was called with', [...]
    toHaveBeenLastCalledWith() lastCalledWith() spy.lastCall, 'to have args', [...]
    toHaveBeenNthCalledWith(n) nthCalledWith() spy.getCall(n-1), 'to have args', [...]
    toHaveReturned() toReturn() 'to have returned'
    toHaveReturnedTimes(n) toReturnTimes() 'to have returned times', n
    toHaveReturnedWith(v) toReturnWith() 'to have returned with', v
    toHaveLastReturnedWith(v) lastReturnedWith() spy.lastCall, 'to have returned', v
    toHaveNthReturnedWith(n,v) nthReturnedWith() spy.getCall(n-1), 'to have returned', v

    Note: The Jest 29 aliases were deprecated in Jest 26 and removed in Jest 30. This codemod handles both.

    Example transformation:

    // Before (Jest)
    expect(mockFn).toHaveBeenCalled();
    expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
    expect(mockFn).toHaveBeenLastCalledWith('final');

    // After (bupkis + @bupkis/sinon)
    expect(mockFn, 'was called');
    expect(mockFn, 'was called with', ['arg1', 'arg2']);
    expect(mockFn.lastCall, 'to have args', ['final']);

    Important: The --sinon flag only transforms assertions. You must manually migrate your mock/spy creation from jest.fn() to Sinon:

    // Before
    const mockFn = jest.fn();

    // After (manual migration required)
    import sinon from 'sinon';
    const mockFn = sinon.spy();

    When mock matchers are detected without --sinon, the codemod will notify you and suggest using the flag.

    import { transform, transformCode } from '@bupkis/from-jest';

    // Transform a code string
    const result = await transformCode(`expect(42).toBe(42);`);
    console.log(result.code); // expect(42, 'to be', 42);

    // Transform with sinon support
    const result = await transformCode(`expect(spy).toHaveBeenCalled();`, {
    sinon: true,
    });
    console.log(result.code); // expect(spy, 'was called');

    // Transform files
    const results = await transform({
    include: ['src/**/*.test.ts'],
    exclude: ['**/node_modules/**'],
    mode: 'best-effort',
    sinon: true, // Enable mock matcher transformation
    write: true,
    });

    Some Jest/Vitest patterns cannot be automatically transformed:

    • Mock/spy creation: jest.fn(), jest.spyOn(), vi.fn() must be manually migrated to Sinon (sinon.spy(), sinon.stub())
    • DOM matchers: @testing-library/jest-dom matchers like toBeInTheDocument
    • Promise matchers: resolves/rejects need restructuring to expectAsync
    • Complex toHaveProperty: When checking property value, not just existence

    These cases will be marked with // TODO: Manual migration needed comments when using --best-effort mode.

    Tip: Mock/spy assertions can be transformed automatically with --sinon. See the Mock/Spy Matchers section.

    Copyright © 2025 Christopher Hiller. Licensed under BlueOak-1.0.0.