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

Cannot dynamic import multiple times a mocked module #7040

Open
6 tasks done
dubzzz opened this issue Dec 6, 2024 · 2 comments
Open
6 tasks done

Cannot dynamic import multiple times a mocked module #7040

dubzzz opened this issue Dec 6, 2024 · 2 comments
Labels
p3-minor-bug An edge case that only affects very specific usage (priority)

Comments

@dubzzz
Copy link
Contributor

dubzzz commented Dec 6, 2024

Describe the bug

When dynamically importing several times the same mocked module, we may fail to import it on subsequent imports.

Reproduction

The reproduction will be clearer than the description above:

// src/main.js
const load = () => import("./constants");
export async function preload() {
  load();
}
export async function compute() {
  return (await load()).default;
}
// test/main.spec.js
import { test, vi ,expect} from "vitest";
import { preload, compute } from "../src/main";

vi.mock("../src/constants.js", () => ({ default: "from-mock" }));

test("sum", async () => {
  preload();
  expect(await compute()).toBe("from-mock"); // failing, we receive the unmocked value, if we drop preload it works
  // if we change compute to do: return { first: (await load()).default, second: (await load()).default };
  // we receive the unmocked and the mocked value
});

Reproduction at: https://github.com/dubzzz/imported-twice-vitest-bug

System Info

System:
    OS: Linux 6.5 Ubuntu 20.04.6 LTS (Focal Fossa)
    CPU: (2) x64 Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
    Memory: 5.50 GB / 7.74 GB
    Container: Yes
    Shell: 5.0.17 - /bin/bash
  Binaries:
    Node: 20.17.0 - ~/nvm/current/bin/node
    Yarn: 1.22.22 - /usr/bin/yarn
    npm: 10.8.2 - ~/nvm/current/bin/npm
    pnpm: 9.11.0 - ~/nvm/current/bin/pnpm
  npmPackages:
    vitest: ^2.1.8 => 2.1.8

Used Package Manager

npm

Validations

@hi-ogawa hi-ogawa added p3-minor-bug An edge case that only affects very specific usage (priority) and removed pending triage labels Dec 12, 2024
@hi-ogawa
Copy link
Contributor

hi-ogawa commented Dec 12, 2024

Thanks for the reproduction. It looks like there is some race condition around VitestMocker

public async resolveMocks() {

public async requestWithMock(url: string, callstack: string[]) {

There's a logic to bail out early when cyclic import, but that got kicked in even though this is not a cyclic import. I don't have a fix yet. For now, I prepared a simpler repro for debugging if anyone wants to look into further.

https://stackblitz.com/edit/github-r4fmamrp?file=test%2Fautomock.spec.js

import { expect, test, vi } from 'vitest';

vi.mock('./factory-target', () => ({ default: 'mocked' }));

test('automock', async () => {
  await import('node:path'); // need this to flush `Vitestmocker.resolveMocks`

  const all = await Promise.all([
    import('./factory-target').then((v) => v.default),
    import('./factory-target').then((v) => v.default),
    import('./factory-target').then((v) => v.default),
  ]);
  expect(all).toMatchInlineSnapshot(`
    [
      "mocked",
      "original",    // <- should be "mocked"
      "original",    // <
    ]
  `);
});
import { expect, test, vi } from 'vitest';

vi.mock('./automock-target');

test('automock', async () => {
  await import('node:path'); // need this to flush `Vitestmocker.resolveMocks`

  const all = await Promise.all([
    import('./automock-target').then((v) => v.default),
    import('./automock-target').then((v) => v.default),
    import('./automock-target').then((v) => v.default),
  ]);
  expect(all).toMatchInlineSnapshot(`
    [
      "original",
      undefined,   // <- should be "original"
      undefined,   // <
    ]
  `);
});

@dubzzz
Copy link
Contributor Author

dubzzz commented Dec 12, 2024

Thanks a lot for your investigation.

I just wanted to share how I found this issue. My test case is far from being the code I had initially 😅

I found this bug with a pretty usual React pattern around lazy loading (written from mobile do sorry for typos):

const Authenticated = lazy(() => import('./Authenticated')

function App() {
  const isAuthenticated = useIsAuthenticated()
  useEffect(() => {
    import('./Authenticated') // fire early load while user still logs
  })
  if (! isAuthenticated) return <Login />
  return <Suspense><Authenticated /></Suspense>
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p3-minor-bug An edge case that only affects very specific usage (priority)
Projects
None yet
Development

No branches or pull requests

2 participants