bun icon indicating copy to clipboard operation
bun copied to clipboard

Restore mock.module using mock.restore not work as expect

Open yukikwi opened this issue 1 year ago • 14 comments

What version of Bun is running?

1.0.20+09d51486e

What platform is your computer?

Linux 6.5.0-14-generic x86_64 x86_64

What steps can reproduce the bug?

I create test file with this code inside

import axios from "axios"
import { describe, expect, it, mock } from "bun:test"

describe("topic", () => {
  it("test case 1", async () => {
    // before mock
    expect((await axios("https://google.com/", { method: "GET" })).status).toBe(200)

    // mock
    mock.module("axios", () => ({
      default: () => {
        return {
          status: 500
        } 
      }
    }));

    // after mock
    expect((await axios("https://google.com/", { method: "GET" })).status).toBe(500)

    // restore mock & test again
    mock.restore()
    expect((await axios("https://google.com/", { method: "GET" })).status).toBe(200)
  })
})

What is the expected behavior?

I expect this test case must pass

What do you see instead?

// before mock expect((await axios("https://google.com/", { method: "GET" })).status).toBe(200) -> this pass

// after mock expect((await axios("https://google.com/", { method: "GET" })).status).toBe(500) -> this pass

// restore mock & test again expect((await axios("https://google.com/", { method: "GET" })).status).toBe(200) -> this fail (value is 500)

Additional information

This one work like what I expected but I think it is not good solution

import axios from "axios"
import path from "path"
import { describe, expect, it, mock } from "bun:test"

describe("topic", () => {
  it("test case 1", async () => {
    // before mock
    expect((await axios("https://google.com/", { method: "GET" })).status).toBe(200)

    // mock
    const axiosBak = axios  <-- add this
    mock.module("axios", () => ({
      default: () => {
        return {
          status: 500
        } 
      }
    }));

    // after mock
    expect((await axios("https://google.com/", { method: "GET" })).status).toBe(500)

    // restore mock & test again
    mock.restore()
    mock.module("axios", () => axiosBak) <-- add this
    expect((await axios("https://google.com/", { method: "GET" })).status).toBe(200)
  })
})

yukikwi avatar Dec 25 '23 07:12 yukikwi

Not only is the restore not working, modules that are mocked will conflict with other testfiles that also need to mock the given module. Not sure what the path forward is for this but its a major blocker

chlorophant avatar Jan 22 '24 06:01 chlorophant

This is the workaround I have been using in the meantime:

import { mock } from "bun:test";

export type MockResult = {
    clear: () => void;
};

/**
 *
 * @param modulePath - the path starting from this files' path.
 * @param renderMocks - function to generate mocks (by their named or default exports)
 * @returns an object
 */
export const mockModule = async (
    modulePath,
    renderMocks: () => Record<string, any>,
): Promise<MockResult> => {
    let original = {
        ...(await import(modulePath)),
    };
    let mocks = renderMocks();
    let result = {
        ...original,
        ...mocks,
    };
    mock.module(modulePath, () => result);
    return {
        clear: () => {
            mock.module(modulePath, () => original);
        },
    };
};

Then for each test suite declare

    let mocks: MockResult[] = [];
    afterEach(() => {
        mocks.forEach((mockResult) => mockResult.clear());
        mocks = [];
    });

Then finally for example in the test

        mocks.push(
            await mockModule("./utils/aws", () => ({
                ensureAwsSsoSession: jest.fn(() => Promise.resolve()),
            })),
        );

I can't stress enough that this is a hacky unstable solution that I am only leveraging because our unit tests would be dead in the water otherwise. Hopefully this helps others until a permanent solution comes through 👍

jpasquers avatar Jan 24 '24 16:01 jpasquers

As a temporary workaround, you can try jest.restoreAllMocks

import {jest} from 'bun:test';

jest.restoreAllMocks();

Jarred-Sumner avatar Jan 24 '24 17:01 Jarred-Sumner

@Jarred-Sumner hmm unfortunately that didn't resolve it for me, or at the very least the way I used it wasn't working. I leveraged it as an afterEach, i.e.

afterEach(() => jest.restoreAllMocks());

And using the standard mock.module (example usage):

        mock.module("./utils/aws", () => ({
            ensureAwsSsoSession: jest.fn(() => Promise.resolve()),
        }));

jpasquers avatar Jan 24 '24 18:01 jpasquers

I'm facing the same issue with mock.module, I also tried mocking the module with require.resolve('path') in order to see if that would create another mock for the same module but with no luck.

Ideally the mock.module should only be valid inside that test, other tests could mock the same module with other values like jest. Global mocks should be added in the preload file IMHO.

cuquo avatar Feb 07 '24 06:02 cuquo

As a temporary workaround, you can try jest.restoreAllMocks

import {jest} from 'bun:test';

jest.restoreAllMocks();

This doesn't work. Any other workarounds other than instantiating a new bun process for every test file?

RichAyotte avatar Jun 22 '24 18:06 RichAyotte

Since #10210 is closed now, we really need the fix for mock.restore or jest.restoreAllMocks… Right now it’s really hard to use bun’s test runner on more or less big project with module mocking without spawning separate process for every test file.

MonsterDeveloper avatar Jul 10 '24 05:07 MonsterDeveloper