swc: `Cannot redefine property` error when using `jest.spyOn` after v1.2.206

Describe the bug

SWC version v1.2.206 is causing our jest setup file to error out when calling jest.spyOn. v1.2.205 does not error out.

Input code

// jest.setup.ts
import * as cache from './packages/foo/src/common/cache'
const redis = new RedisMock(7481)
beforeEach(() => {
  jest.spyOn(cache, 'getRedis').mockResolvedValue(redis)
})

// cache.ts
import memoize from 'p-memoize'
import { getConfig } from './config'
export const getRedis = memoize(async () => {
  const config = await getConfig()
  return new Redis(config.redisUrl)
})

Config

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true
    },
    "target": "es2021",
    "keepClassNames": true
  }
}

Playground link

No response

Expected behavior

jest.spyOn should stub cache.getRedis

Actual behavior

jest.spyOn fails with the following error:

TypeError: Cannot redefine property: getRedis
    at Function.defineProperty (<anonymous>)
    at ModuleMocker.spyOn (/home/circleci/project/node_modules/jest-mock/build/index.js:820:16)
    at Object.spyOn (/home/circleci/project/jest.setup.ts:26:8)
    at Promise.then.completed (/home/circleci/project/node_modules/jest-circus/build/utils.js:333:28)
    at new Promise (<anonymous>)
    at callAsyncCircusFn (/home/circleci/project/node_modules/jest-circus/build/utils.js:259:10)
    at _callCircusHook (/home/circleci/project/node_modules/jest-circus/build/run.js:240:40)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at _runTest (/home/circleci/project/node_modules/jest-circus/build/run.js:202:5)

Version

v1.2.206+

Additional context

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 5
  • Comments: 29 (19 by maintainers)

Most upvoted comments

If this is truly a bugfix and not a bug, you should update the intro text here: https://swc.rs/docs/usage/jest

This is no longer a drop in replacement for ts-jest as reverting to ts-jest reverts to the previous behavior

When I set module: commonjs what does it mean? Do I get CJS or ESM? Both, and neither.

It is CJS, because the output code has exports/module.exports. It is not CJS, because it is transformed from ESM. Users are using ESM syntax, such as export default, export { foo }, etc. Users expect ESM behaviour, such as export is live-binding, export cannot be overwritten. These are ESM features. We simulate ESM behaviour in CJS.

If the user expects CJS, it is a misuse to use these syntaxes. There is no equivalent export syntax in ESM. However, the exports.foo = expr syntax still works, as will TS’s export = expr.

But what if the user really wants ESM and wants to test in jest? This is jest’s problem, which is equivalent to how jest is used in an ESM environment.

@kdy1 I agree that the new behavior may not be a bug, but rather is fixing a previous bug, and the change results in a stricter / more correct adherence to the esm specification, but an argument can be made that this is a breaking change, and hence warrants a major version bump.

Furthermore, as @ldiqual said, I’m also using typescript, and our tsconfig.json has module set to CommonJS, module resolution to node, and target as es2020, so I’m not sure how esm is even coming into the picture.

@ldiqual If you do not expect an esm behaviour, do not use esm syntax. use CJS module.exports, or CTS export synatx.

function getRedis() { }
export = {
    getRedis,
    foo,
    bar
}