Skip to content

@swc/jest does not hoist mocks if the TypeScript file imports @jest/globals #10325

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mogzol opened this issue Mar 28, 2025 · 2 comments
Open
Milestone

Comments

@mogzol
Copy link

mogzol commented Mar 28, 2025

Problem

I have a TypeScript Jest test that imports jest from @jest/globals, and also mocks some modules. When compiled with swc, the mock is not hoisted to the top of the file like it should be. Removing the import for @jest/globals fixes the issue, but then Jest types don't work. Obviously that can be fixed by installing the @types/jest package instead of explicitly importing the globals, but I would expect either way to work, since Jest mentions both ways in it's documentation, and ts-jest supports both methods.

Reproduction

Say I have two files, example.ts, and its test, example.test.ts:

// example.ts
export function doSomething(): string {
  return "Module is NOT mocked!";
}
// example.test.ts
import { jest } from "@jest/globals";
import { doSomething } from "./example";

jest.mock("./example", () => ({
  doSomething: () => "Module is mocked!"
}))

test("mock works", () => {
  const result = doSomething();
  expect(result).toBe("Module is mocked!");
})

If I run Jest (configured to use @swc/jest) on the test, it fails because doSomething is imported from ./example before the mock is created. This can be seen by using the swc CLI with the same configuration that Jest uses:

> npx swc -C jsc.transform.hidden.jest=true -C module.type=commonjs example.test.ts

Outputs:

"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
var _globals = require("@jest/globals");
var _example = require("./example");
_globals.jest.mock("./example", function() {
    return {
        doSomething: function() {
            return "Module is mocked!";
        }
    };
});
test("mock works", function() {
    var result = (0, _example.doSomething)();
    expect(result).toBe("Module is mocked!");
});

Notice how the mock comes after the require statements.

Now, if I remove the @jest/globals import, and run Jest again, the test passes. Using the swc CLI we can see that the mock is properly hoisted to the top:

> npx swc -C jsc.transform.hidden.jest=true -C module.type=commonjs example.test.ts
"use strict";
jest.mock("./example", function() {
    return {
        doSomething: function() {
            return "Module is mocked!";
        }
    };
});
Object.defineProperty(exports, "__esModule", {
    value: true
});
var _example = require("./example");
test("mock works", function() {
    var result = (0, _example.doSomething)();
    expect(result).toBe("Module is mocked!");
});

Expected Behaviour

@swc/jest should work with TypeScript tests that explicitly import globals, the same way it works when the globals are implicit from @types/jest.

@kdy1 kdy1 self-assigned this Apr 3, 2025
@kdy1 kdy1 transferred this issue from swc-project/pkgs Apr 8, 2025
@kdy1 kdy1 removed their assignment May 7, 2025
@jonaslalin

This comment has been minimized.

@kdy1 kdy1 added this to the Planned milestone May 20, 2025
@jonaslalin
Copy link

One workaround to get the types from @jest/globals instead of @types/jest, is to add a jest-globals.d.ts file, and have that included in tsconfig.json:

import * as jestGlobals from '@jest/globals'
import type { ClassLike, FunctionLike, UnknownFunction } from 'jest-mock'

declare global {
  const expect: typeof jestGlobals.expect
  const it: typeof jestGlobals.it
  const test: typeof jestGlobals.test
  const fit: typeof jestGlobals.fit
  const xit: typeof jestGlobals.xit
  const xtest: typeof jestGlobals.xtest
  const describe: typeof jestGlobals.describe
  const xdescribe: typeof jestGlobals.xdescribe
  const fdescribe: typeof jestGlobals.fdescribe
  const beforeAll: typeof jestGlobals.beforeAll
  const beforeEach: typeof jestGlobals.beforeEach
  const afterEach: typeof jestGlobals.afterEach
  const afterAll: typeof jestGlobals.afterAll
  const jest: typeof jestGlobals.jest

  // biome-ignore lint/suspicious/noExplicitAny: fail function needs to accept any type to match Jest's implementation
  function fail(error?: any): never

  // biome-ignore lint/style/noNamespace: Namespace is required to match Jest's type system structure
  namespace jest {
    type Mock<T extends FunctionLike = UnknownFunction> = jestGlobals.jest.Mock<T>
    type Mocked<T extends object> = jestGlobals.jest.Mocked<T>
    type MockedClass<T extends ClassLike> = jestGlobals.jest.MockedClass<T>
    type MockedFunction<T extends FunctionLike> = jestGlobals.jest.MockedFunction<T>
    type MockedObject<T extends object> = jestGlobals.jest.MockedObject<T>
    type Replaced<T> = jestGlobals.jest.Replaced<T>
    type Spied<T extends ClassLike | FunctionLike> = jestGlobals.jest.Spied<T>
    type SpiedClass<T extends ClassLike> = jestGlobals.jest.SpiedClass<T>
    type SpiedFunction<T extends FunctionLike> = jestGlobals.jest.SpiedFunction<T>
    type SpiedGetter<T> = jestGlobals.jest.SpiedGetter<T>
    type SpiedSetter<T> = jestGlobals.jest.SpiedSetter<T>
  }
}

But the proper fix would be to fix the hoisting issue and explicit imports from @jest/globals

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants