floriankapaun
floriankapaun

Reputation: 827

Vitest mock modules function in only one test and use the actual function in others

The following is an abstraction of my problem and thus does not make too much sense:

Given I have a simple utility callMethodIf that's returning the return of another imported method (blackbox).

~~/utils/call-method-if.js:

import { blackbox } from '~~/utils/blackbox';

export const callMethodIf = (condition) => {
    return blackbox(condition);
};

~~/utils/blackbox.js:

export const blackbox = (condition) => {
    return { called: condition };
};

How would I run one test case which calls the actual implementation of blackbox() and another one where I mock the return value of blackbox()?

I tried to do it that way:

import { describe, expect, it } from 'vitest';

import { callMethodIf } from '~~/utils/call-method-if';

describe('Call method if', () => {
    it('returns "called: true" if condition is true', () => {
        const result = callMethodIf(true);
        expect(result).toEqual({ called: true });
    });

    it('returns mocked blackbox return object', () => {
        vi.mock('~~/utils/blackbox', () => ({
            blackbox: vi.fn().mockReturnValue({ mock: true })
        }));
        const result = callMethodIf(false);
        expect(result).toEqual({ mock: true });
    });
});

Both tests work if I run only one of them, but they don't work when combined.

Running vi.clearAllMocks() or vi.resetAllMocks() don't help.

Defining a global mock and overwriting it in my first test doesn't work either:

import { describe, expect, it } from 'vitest';

import { callMethodIf } from '~~/utils/call-method-if';

vi.mock('~~/utils/blackbox', () => ({
    blackbox: vi.fn().mockReturnValue({ mock: true })
}));

describe('Call method if', () => {
    it('returns "called: true" if condition is true', () => {
        vi.mock('~~/utils/blackbox', async () => ({
            blackbox: (await vi.importActual('~~/utils/blackbox')).blackbox
        }));
        const result = callMethodIf(true);
        expect(result).toEqual({ called: true });
    });

    it('returns mocked blackbox return object', () => {
        const result = callMethodIf(false);
        expect(result).toEqual({ mock: true });
    });
});

Upvotes: 16

Views: 43034

Answers (2)

Joep Verhoeven
Joep Verhoeven

Reputation: 128

I also ran in to this problem with using Vite. After a lot of trail and error I have managed to get the mocking of a module function working by using the following code:

vi.mock('@/models/generated/graphql')

describe('MyComponent works as expected', () => {
  it('Shows loading when loading', async () => {
    const graphql = await import('@/models/generated/graphql')
    graphql.useGetAllQuery = vi.fn().mockReturnValue({ loading: true, error: null, data: null })

    render(<MyComponent />)

    expect(screen.findByTestId('my-component-loading')).toBeTruthy()
  })
}

The function that I am mocking is this one (which is auto generated for my Graphql service):

export function useGetAllQuery(baseOptions?: Apollo.QueryHookOptions<GetAllQuery, GetAllQueryVariables>) {
        const options = {...defaultOptions, ...baseOptions}
        return Apollo.useQuery<GetAllQuery, GetAllQueryVariables>(GetAllDocument, options);
      }

I do not really understand why this works, but it does. I hope that this code snippet might help some.

Upvotes: 7

floriankapaun
floriankapaun

Reputation: 827

Okay, after lots of trial and error I finally got it to work. I can't really tell why my previous tries do not work tough.

Working solution:

import { describe, expect, it } from 'vitest';

import { callMethodIf } from '~~/utils/call-method-if';

vi.mock('~~/utils/blackbox');

describe('Call method if', () => {
    it('returns "called: true" if condition is true', async () => {
        const blackbox = await import('~~/utils/blackbox');
        blackbox.blackbox = (await vi.importActual('~~/utils/blackbox')).blackbox;
        const result = callMethodIf(true);
        expect(result).toEqual({ called: true });
    });

    it('returns mocked blackbox return object', async () => {
        const blackbox = await import('~~/utils/blackbox');
        blackbox.blackbox = vi.fn().mockReturnValue({ mock: true });
        const result = callMethodIf(false);
        expect(result).toEqual({ mock: true });
    });
});

When using TypeScript consider typing the importActual() return like that:

blackbox.blackbox = (await vi.importActual<typeof import('~~/utils/blackbox')>('~~/utils/blackbox')).blackbox;

Upvotes: 16

Related Questions