Skip to content
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

Jest commonjs gives ReferenceError: Cannot access before initialization when jest.mock and exported class #92

Open
smvv opened this issue Nov 3, 2023 · 2 comments

Comments

@smvv
Copy link

smvv commented Nov 3, 2023

I've read #79 but I think I'm seeing a related issue.

minimal reproducible code

There are two input source files:

// 1. application/web/foo.test.ts 
const run = jest.fn()                    
                                         
const fakeBar = {                        
    __esModule: true,                    
    default: {                           
        run,                             
    },                                   
}                                        
jest.mock('./bar', () => fakeBar)        
                                         
import bar from './bar'                  
                                         
describe('Test example', () => {         
    it('has a passing test', () => {           
        expect(bar.run).not.toBeCalled() 
    })                                   
})

// 2. application/web/bar.ts
export default class Bar {
	static run() {}
}                                

The swc config used for transforming TS to JS in jest is:

{                                                     
  "$schema": "http://json.schemastore.org/swcrc",     
  "sourceMaps": true,                                 
  "jsc": {                                            
    "parser": {                                       
      "syntax": "typescript",                         
      "tsx": true,                                    
      "dynamicImport": true,                          
      "decorators": true                              
    },                                                
    "preserveAllComments": true,                      
    "transform": null,                                
    "target": "es2017",                               
    "loose": false,                                   
    "externalHelpers": false,                         
    "keepClassNames": false,                          
    "experimental": {                                 
      "plugins": [                                    
        [                                             
          "swc_mut_cjs_exports",                      
          {}                                          
        ]                                             
      ]                                               
    }                                                 
  },                                                  
  "module": {                                         
    "type": "commonjs"                                
  }                                                   
}                                                     

The tsc config is:

{
  "transpileOnly": true,
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "jsx": "react-jsx",
    "esModuleInterop": true,
    "sourceMap": false,
    "allowJs": true
  }
}

tsc transpiled

Test output:

+ TS_LOADER=tsc yarn -s test application/web/foo.test.ts
 PASS  application/web/foo.test.ts
  Test example
    ✓ has a passing test (1 ms)
// tsc js output of foo.test.ts
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const run = jest.fn();
const fakeBar = {
  __esModule: true,
  default: {
    run,
  },
};
jest.mock("./bar", () => fakeBar);
const bar_1 = tslib_1.__importDefault(require("./bar"));
describe("Test example", () => {
  it("has a passing test", () => {
    expect(bar_1.default.run).not.toBeCalled();
  });
});

// tsc js output of bar.ts
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Bar {
  static run() {}
}
exports.default = Bar;

swc transpiled

Test output:

+ TS_LOADER=swc yarn -s test application/web/foo.test.ts
 FAIL  application/web/foo.test.ts
  ● Test suite failed to run

    ReferenceError: Cannot access 'fakeBar' before initialization

       7 | 	},
       8 | }
    >  9 | jest.mock('./bar', () => fakeBar)
         |                          ^
      10 |
      11 | import bar from './bar'
      12 |

      at fakeBar (application/web/foo.test.ts:9:26)
      at Object.<anonymous> (application/web/foo.test.ts:6:39)
// swc js output of foo.test.ts
"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true,
});
const _bar = _interop_require_default(require("./bar"));
function _interop_require_default(obj) {
  return obj && obj.__esModule
    ? obj
    : {
        default: obj,
      };
}
const run = jest.fn();
const fakeBar = {
  __esModule: true,
  default: {
    run,
  },
};
jest.mock("./bar", () => fakeBar);
describe("Test example", () => {
  it("has a passing test", () => {
    expect(_bar.default.run).not.toBeCalled();
  });
})

// swc js output of bar.ts
"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true,
});
Object.defineProperty(exports, "default", {
  enumerable: true,
  get() {
    return Bar;
  },
  set(v) {
    Bar = v;
  },
  configurable: true,
});
var Bar;
Bar = class Bar {
  static run() {}
};

Notes

SWC version info:

@swc/core@1.3.95
@swc/jest@0.2.29
@swc/helpers@0.5.3
swc-loader@0.2.3
swc_mut_cjs_exports@0.86.17

Tsc generates this import order:

jest.mock("./bar", () => fakeBar);
const bar_1 = tslib_1.__importDefault(require("./bar"));

while swc generates:

const _bar = _interop_require_default(require("./bar"));
function _interop_require_default(obj) { ... }
const run = jest.fn();
const fakeBar = { ... }
jest.mock("./bar", () => fakeBar);

Could that ordering be an issue that causes the runtime error?

I'm using the jest preset ts-jest with a custom transformer (to allow switching at runtime using TS_LOADER between tsc and swc).

Could a jest plugin like this hoisting of mock calls be related?

I hope this issue contains all the information. Let me know if a reproducible git repo would be helpful to narrow down the problem. Thank you!

@smvv smvv changed the title Jest commonjs gives ReferenceError: Cannot access before initialization when jest.mock and exoprrted class Jest commonjs gives ReferenceError: Cannot access before initialization when jest.mock and exported class Nov 3, 2023
@smvv
Copy link
Author

smvv commented Nov 3, 2023

note: it seems that esbuild-jest is transforming the test source file using babel when the source code contains the string ock(: aelbore/esbuild-jest@e94d4c1#diff-a2a171449d862fe29692ce031981047d7ab755ae7f84c707aef80701b3ea0c80R34

@magic-akari
Copy link
Owner

Could that ordering be an issue that causes the runtime error?

Yes. All import statements will be hoisted.
In fact, It's a bug of tsc. see microsoft/TypeScript#16166

Could a jest plugin like this hoisting of mock calls be related?

It's possible to hoist jest related methods.

I haven't tested it myself. Have you tried manually editing the statement order of the swc compiled files?
If it is feasible, It's better to send a feature request to https://github.com/swc-project/plugins/tree/main/packages/jest , since it is related to import hoisting and this repository focuses on the mutability of export.

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

No branches or pull requests

2 participants