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

[Bug]: Mocking Partials (in with ES6 modules) doesn't work as per documentation #15100

Open
harvzor opened this issue May 31, 2024 · 5 comments

Comments

@harvzor
Copy link

harvzor commented May 31, 2024

Version

29.7.0

Steps to reproduce

Stackblitz playground: https://stackblitz.com/edit/stackblitz-starters-nybxbq

Result:

image


Follow the documentation available here: https://jestjs.io/docs/mock-functions#mocking-partials

  1. Create a file called ./foo-bar-baz.js:
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';
  1. Create a file called ./tests/test.js:
import defaultExport, { bar, foo } from '../foo-bar-baz';
import { jest } from '@jest/globals';

jest.mock('../foo-bar-baz', () => {
  const originalModule = jest.requireActual('../foo-bar-baz');

  //Mock the default export and named export 'foo'
  return {
    __esModule: true,
    ...originalModule,
    default: jest.fn(() => 'mocked baz'),
    foo: 'mocked foo',
  };
});

test('should do a partial mock', () => {
  const defaultExportResult = defaultExport();
  expect(defaultExportResult).toBe('mocked baz');
  expect(defaultExport).toHaveBeenCalled();

  expect(foo).toBe('mocked foo');
  expect(bar()).toBe('bar');
});
  1. Make sure the package.json is configured correctly (so Jest works with ES6 modules):
{
  "name": "jest-starter",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
  },
  "devDependencies": {
    "jest": "^29.7.0"
  },
  "jest": {
    "transform": {}
  }
}
  1. Run the test with npm test
  2. Inspect results

Expected behavior

  • I expect the tests to pass
  • defaultExportResult() should return mocked baz instead of baz

Actual behavior

Test fails, and it appears the mocks simply aren't run:

npm test

> jest-starter@0.0.0 test
> node --experimental-vm-modules node_modules/jest/bin/jest.js

(node:32) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 FAIL  ./test.js
  ✕ should do a partial mock (5 ms)

  ● should do a partial mock

    Expected value   undefined
    Received:
      undefined
    
    Message:
      expect(received).toBe(expected) // Object.is equality
    
    Expected: "mocked baz"
    Received: "baz"

    Difference:

    Compared values have no visual difference.Error:

    

      at processResult (node_modules/expect/build/index.js:284:19)
      at throwingMatcher (node_modules/expect/build/index.js:336:16)
      at null.<anonymous> (test.js:29:31)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.976 s, estimated 1 s
Ran all test suites.

Additional context

Pulling down the StackBlitz and running it locally produces the same result.

Also, in my own repository, I have this same issue.

Environment

This is what my local environment reports:

  System:
    OS: Windows 10 10.0.19045
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
  Binaries:
    Node: 18.20.3 - C:\Program Files\nodejs\node.EXE
    npm: 10.7.0 - C:\Program Files\nodejs\npm.CMD
  npmPackages:
    jest: ^29.7.0 => 29.7.0

This is what StackBlitz reports:

  System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
  Binaries:
    Node: 18.20.3 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.3 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/bin/pnpm
  npmPackages:
    jest: ^29.7.0 => 29.7.0 
@harvzor
Copy link
Author

harvzor commented May 31, 2024

This works:

import { jest } from '@jest/globals';

jest.unstable_mockModule('../foo-bar-baz', () => {
  return {
    bar: () => 'mocked bar',
  };
});

const { bar } = await import('../foo-bar-baz');

test('should do a partial mock', () => {
  expect(bar()).toBe('mocked bar');
});

Docs for unstable_mockModule are available here: https://jestjs.io/docs/ecmascript-modules#module-mocking-in-esm

Trying to make the implementation exactly the same as in the original example, I think this is the correct code:

import { jest } from '@jest/globals';

jest.unstable_mockModule('../foo-bar-baz', async () => {
  const originalModule = await import('../foo-bar-baz');

  return {
    ...originalModule,
    default: jest.fn(() => 'mocked baz'),
    foo: 'mocked foo',
  };
});

const { default: defaultExport, bar, foo } = await import('../foo-bar-baz');

test('should do a partial mock', () => {
  const defaultExportResult = defaultExport();
  expect(defaultExportResult).toBe('mocked baz');
  expect(defaultExport).toHaveBeenCalled();

  expect(foo).toBe('mocked foo');
  expect(bar()).toBe('bar');
});

However, the test never finishes. Seems this issue was reported a while ago: #13851

@mrazauskas
Copy link
Contributor

As you see CJS has jest.requireActual(), but it can’t work in ESM. Similar async method for imports is needed as mentioned in the meta issue: #9430

@harvzor
Copy link
Author

harvzor commented May 31, 2024

It seems that #10025 is tracking a potential fix for this issue (since 2020)

Since requireActual() does not work with partial ES6 module mocks, probably the documentation should say this.

Should I make a PR?

@harvzor
Copy link
Author

harvzor commented May 31, 2024

Similar code works in Vitest using a special method called importOriginal(): https://stackblitz.com/edit/vitejs-vite-fcr3z9

Vitest supports partial mocks but has a caveat that's well explained in the docs:

It is not possible to mock the foo method from the outside because it is referenced directly. So this code will have no effect on the foo call inside foobar (but it will affect the foo call in other modules):

And then goes on to write:

This is the intended behaviour. It is usually a sign of bad code when mocking is involved in such a manner.

https://vitest.dev/guide/mocking.html#mocking-pitfalls

Probably partial mocks should be generally avoided.

@mrazauskas
Copy link
Contributor

require() does not work in ESM as well as requireActual(). It might make sense to mention that partial mocking is not yet supported, but that is mentioned in #9430 and the ECMAScript Modules page already links to this issue. In a way several features are not yet working. Not sure if it worth mentioning them in documentation.

As a side note. Jest and Vitest have very different approach to ESM. In Jest a plain JavaScript ESM is simply executed, in Vitest you will see transform time being reported. I don’t know what is that transformation about, but that is: 1. waste of time; 2. not ESM as implemented by Node.js (for instance require global gets injected).

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

No branches or pull requests

2 participants