Skip to content

[Bug]: Jest mock not mocking #15741

@andreymaklakov

Description

@andreymaklakov

Version

29.7.0, 30.0.4

Steps to reproduce

its react app, react version is 18.2.0, bundler is vite

jest config

module.exports = {
    setupFilesAfterEnv: ['<rootDir>/testconfig/setupTest.tsx'],
    testEnvironment: 'jest-fixed-jsdom',
    testEnvironmentOptions: {
        customExportConditions: ['browsers']
    },
    modulePaths: ['<rootDir>src'],
    testMatch: ['<rootDir>src/**/*(*.)@(spec|test).[tj]s?(x)'],
    rootDir: '.',
    transform: {
        '^.+\\.(ts|tsx|js)$': 'babel-jest'
    },
    transformIgnorePatterns: ['/node_modules/(?!4game-ui)'],
    moduleDirectories: ['node_modules'],
    clearMocks: true,
    moduleNameMapper: {
        '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
        '^@/(.*)$': '<rootDir>src/$1'
    },
    globals: {
        __IS_DEV__: true,
        __ENV__: 'development',
        __AREA__: 'ru',
        __IS_TESTING__: true,
        TextEncoder: TextEncoder,
        TextDecoder: TextDecoder,
        Response: Response
    }
};

setup tests

import '@testing-library/react';
import '@testing-library/jest-dom';
import { fetch, Headers, Request, Response } from 'cross-fetch';
import dayjs from 'dayjs';
import localeRu from 'dayjs/locale/ru';
import { setupServer } from 'msw/node';

import { store } from '@/app/store';
import { baseApi } from '@/shared/api';

dayjs.locale(localeRu);

global.fetch = fetch;
global.Headers = Headers;
global.Request = Request;
global.Response = Response;

export const server = setupServer();
beforeAll(() => {
    // Enable API mocking before tests.
    server.listen();
    // Disable act warning
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-expect-error
    global.IS_REACT_ACT_ENVIRONMENT = false;
});
// Clear query cache
beforeEach(() => {
    store.dispatch(baseApi.util.resetApiState());
});
// Reset any runtime request handlers we may add during the tests.
afterEach(() => {
    server.resetHandlers();
});
// Disable API mocking after the tests are done.
afterAll(() => server.close());

jest.mock('@tanstack/react-router', () => ({
    ...jest.requireActual('@tanstack/react-router'),
    useSearch: jest.fn(),
    useNavigate: jest.fn(),
    useRouterState: jest.fn(),
    useBlocker: jest.fn(),
    useParams: jest.fn(),
    useLocation: jest.fn()
}));

jest.mock('@ckeditor/ckeditor5-react', () => ({
    CKEditor: jest.fn()
}));

jest.mock('@ckeditor/ckeditor5-editor-balloon', () => ({
    BalloonEditor: jest.fn()
}));

jest.mock('@ckeditor/ckeditor5-essentials', () => ({
    Essentials: jest.fn()
}));

jest.mock('@ckeditor/ckeditor5-link', () => ({
    Link: jest.fn(),
    AutoLink: jest.fn()
}));

jest.mock('@ckeditor/ckeditor5-markdown-gfm', () => ({
    Markdown: jest.fn()
}));

jest.mock('@ckeditor/ckeditor5-paragraph', () => ({
    Paragraph: jest.fn()
}));

jest.mock('@/shared/ui/DoughnutChart', () => ({
    __esModule: true,
    default: () => <div>DoughnutChart</div>
}));
jest.mock('@/shared/ui/DoughnutChart/ui/DoughnutChartSkeleton', () => ({
    __esModule: true,
    default: () => <div>DoughnutChartSkeleton</div>
}));
jest.mock('@/shared/lib/components/ClassicEditor', () => ({
    __esModule: true,
    default: ({ value }: { value: string }) => (
        <div data-testid="ClassicEditor">{value}</div>
    )
}));

class DataTransfer {
    items: Set<File> & {
        remove: typeof Set.prototype.delete;
    };
    files: Set<File>;
    data: Map<string, unknown>;

    constructor() {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-expect-error
        this.items = new Set();
        this.items.remove = this.items.delete;
        this.files = this.items;
        this.data = new Map();
    }

    setData(format: string, data: unknown) {
        this.data.set(format, data);
    }
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
global.DataTransfer = DataTransfer;

component test

import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { useFields } from '@/shared/lib/form';
import { componentRender } from '@/shared/lib/test/componentRender';

import { testIds } from '../../model/testIds/articleFormHeaderTestIds';
import { ArticleFormHeader } from './ArticleFormHeader';

const testId = 'ArticleFormHeader';
const toggleOpenTemplatesList = jest.fn();
const updateEditor = jest.fn();

const props = {
    toggleOpenTemplatesList,
    isTemplatesListOpen: true,
    isNew: false,
    createTemplate: <div>createTemplate</div>,
    updateEditor,
    ['data-testid']: testId
};

const reset = jest.fn();
const setValue = jest.fn();
const isActive = true;

jest.mock('@/shared/lib/form', () => ({
    ...jest.requireActual('@/shared/lib/form'),
    useFields: jest.fn()
}));

const mockUseFields = jest.mocked<Mocked<Partial<typeof useFields>>>(useFields);

beforeEach(() => {
    mockUseFields.mockReturnValue({
        value: () => isActive,
        formState: {
            isSubmitting: false,
            isValid: true,
            isDirty: true
        },
        reset,
        setValue
    });
});

describe('ArticleFormHeader', () => {
    test('component renders', async () => {
        componentRender(<ArticleFormHeader {...props} />);

        expect(await screen.findByTestId(testId)).toBeInTheDocument();
    });

    test('toggleOpenTemplatesList to be called on template open icon click', async () => {
        componentRender(<ArticleFormHeader {...props} />);

        await userEvent.click(await screen.findByTestId(testIds.TemplateIcon));

        expect(toggleOpenTemplatesList).toHaveBeenCalled();
    });

    test('resets form on cancel btn click', async () => {
        componentRender(<ArticleFormHeader {...props} />);

        await userEvent.click(await screen.findByTestId(testIds.Cancel));

        expect(reset).toHaveBeenCalled();
        expect(updateEditor).toHaveBeenCalled();
    });

    test('sets new value on visibility change', async () => {
        componentRender(<ArticleFormHeader {...props} />);

        await userEvent.click(
            await screen.findByTestId(testIds.VisibilitySwitcher)
        );

        expect(setValue).toHaveBeenCalledWith('isActive', !isActive);
    });

    test('submit btn is not disabled when form is valid, not submittin and is dirty', async () => {
        componentRender(<ArticleFormHeader {...props} />);

        expect(await screen.findByTestId(testIds.Submit)).not.toBeDisabled();
    });

    test('submit btn is disabled when form is not valid', async () => {
        mockUseFields.mockReturnValue({
            value: () => isActive,
            formState: {
                isSubmitting: false,
                isValid: false,
                isDirty: true
            },
            reset,
            setValue
        });

        componentRender(<ArticleFormHeader {...props} />);

        expect(await screen.findByTestId(testIds.Submit)).toBeDisabled();
    });

    test('submit btn is disabled when form is submitting', async () => {
        mockUseFields.mockReturnValue({
            value: () => isActive,
            formState: {
                isSubmitting: true,
                isValid: true,
                isDirty: true
            },
            reset,
            setValue
        });

        componentRender(<ArticleFormHeader {...props} />);

        expect(await screen.findByTestId(testIds.Submit)).toBeDisabled();
    });

    test('submit btn is disabled when form is not dirty', async () => {
        mockUseFields.mockReturnValue({
            value: () => isActive,
            formState: {
                isSubmitting: false,
                isValid: true,
                isDirty: false
            },
            reset,
            setValue
        });

        componentRender(<ArticleFormHeader {...props} />);

        expect(await screen.findByTestId(testIds.Submit)).toBeDisabled();
    });
});

versions, same with 29.7.0

"jest": "^30.0.4",
"babel-jest": "^30.0.4",
 "@types/jest": "^30.0.0",
 "@testing-library/jest-dom": "^6.6.3",
 "@testing-library/react": "^16.3.0",
  "@testing-library/user-event": "^14.6.1",

Expected behavior

jest.mock is mocking

Actual behavior

jest.mock is not mocking when it called in test file, but working when called in setupTests file

before all mocks was working fine, but last month I wrote a lot of new code and just now started to cover it with tests, and I discovered that old tests with mocks are failing if mock is in setupTests file and in test file I rewrite the same mock(it started to take mock from setupTests, not from file)
I fixed all this tests, removed mocks from setupTests

But in new tests I just created mocks in test file not working at all, only works if I put mock in setupTests, but I need to make different mocks for each component

maybe its because of a lot of tests in the app

Additional context

No response

Environment

System:
    OS: macOS 15.5
    CPU: (8) arm64 Apple M1 Pro
  Binaries:
    Node: 22.14.0 - /usr/local/bin/node
    Yarn: 1.22.22 - /usr/local/bin/yarn
    npm: 10.9.2 - /usr/local/bin/npm
    pnpm: 10.13.1 - /usr/local/bin/pnpm
  npmPackages:
    jest: ^30.0.4 => 30.0.4

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions