You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Cancel按钮测试失败?React组件测试求助

问题:Cancel按钮测试未触发预期的onCancel回调

我编写了两个测试用例,分别测试ChooseLanguageModal组件的Download按钮(对应handleDownload逻辑)和Cancel按钮(对应handleCancel逻辑)。目前Download按钮测试可正常通过,但Cancel按钮测试始终失败——即便单独运行该测试用例也无法通过,报错提示handleCancel未被调用。

组件代码

import React from 'react';
import { Button, Modal, ModalFooter } from 'react-bootstrap';
import ReactDOM from 'react-dom';

import Select from 'controls/Select/Select';
import { getModalRoot } from 'sd/components/layout/admin/forms/FvReplaceFormModal/helpers';
import { DOWNLOAD_BUTTON_TEXT, CANCEL_BUTTON_TEXT } from 'sd/constants/ModalWindowConstants';

import 'sd/components/layout/admin/forms/FvReplaceFormModal/style.scss';
import type { SelectOption } from 'shared/types/General';

const { Body: ModalBody, Header: ModalHeader, Title: ModalTitle } = Modal;

export interface ChooseLanguageModalProps {
    languageList: SelectOption[];
    onDownloadLanguage: (value?: string) => void;
    onDownload: () => void;
    onCancel?: () => void;
}

const HEADER_TITLE = 'Choose language page';
const CHOOSE_LANGUAGE_LABEL = 'Choose language';

export const ChooseLanguageModal = (props: ChooseLanguageModalProps) => {
    const { languageList } = props;

    const onChangeLanguage = (value?: string | undefined) => {
        const { onDownloadLanguage } = props;
        onDownloadLanguage(value);
    };

    const handleCancel = async () => {
        await hideChooseLanguageModal();
    };

    const handleDownload = async () => {
        const { onDownload } = props;
        onDownload();

        await hideChooseLanguageModal();
    };

    return (
        <Modal
            show
            backdrop="static"
            animation={false}
            container={getModalRoot()}
            onHide={() => hideChooseLanguageModal()}
        >
            <ModalHeader closeButton>
                <ModalTitle>{HEADER_TITLE}</ModalTitle>
            </ModalHeader>
            <ModalBody>
                <div>
                    <p>This project has one or more languages set up in the Translation Manager.</p>
                    <p>
                        To download the translation in the BRD export, select one language from the drop-down below.
                        English will always be shown as the base language.
                    </p>
                    <p>
                        If a language is selected, additional columns will be added to display the appropriate
                        translation for labels, rules and text messages.
                    </p>
                    <p>You may click Download without selecting a language to export the default language.</p>
                </div>
                <div>{CHOOSE_LANGUAGE_LABEL}</div>
                <div>
                    <Select
                        clearable={false}
                        canEnterFreeText={false}
                        searchable={false}
                        options={languageList}
                        onChange={onChangeLanguage}
                    />
                </div>
            </ModalBody>
            <ModalFooter>
                <Button bsStyle="primary" onClick={handleDownload}>
                    {DOWNLOAD_BUTTON_TEXT}
                </Button>
                <Button onClick={handleCancel}>{CANCEL_BUTTON_TEXT}</Button>
            </ModalFooter>
        </Modal>
    );
};

export async function showChooseLanguageModal(props: ChooseLanguageModalProps): Promise<void> {
    await ReactDOM.render(<ChooseLanguageModal {...props} />, getModalRoot());
}

export async function hideChooseLanguageModal(): Promise<void> {
    const modalRoot = getModalRoot();
    modalRoot && ReactDOM.unmountComponentAtNode(modalRoot);
}

测试代码

import { render, fireEvent, screen } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';

import { ChooseLanguageModal } from '../ChooseLanguageWindow.react';

describe('ChooseLanguageModal', () => {
    const languageList = [
        { id: '1', value: 'en', name: 'English' },
        { id: '2', value: 'fr', name: 'French' },
        { id: '3', value: 'es', name: 'Spanish' },
    ];
    const onDownloadLanguage = jest.fn();
    const handleDownload = jest.fn();
    const handleCancel = jest.fn();

    test('should call onDownload when download button is clicked', async () => {
        await act(async () => {
            render(
                <ChooseLanguageModal
                    languageList={languageList}
                    onDownloadLanguage={onDownloadLanguage}
                    onDownload={handleDownload}
                    onCancel={handleCancel}
                />
            );
        });

        const downloadButton = screen.getByText('Download');
        fireEvent.click(downloadButton);

        expect(handleDownload).toHaveBeenCalled();
    });

    test('should call onCancel when cancel button is clicked', async () => {
        await act(async () => {
            render(
                <ChooseLanguageModal
                    languageList={languageList}
                    onDownloadLanguage={onDownloadLanguage}
                    onDownload={handleDownload}
                    onCancel={handleCancel}
                />
            );
        });

        const cancelButton = screen.getByText('Cancel');
        fireEvent.click(cancelButton);

        expect(handleCancel).toHaveBeenCalled();
    });
});

报错信息

ChooseLanguageModal › should call onCancel when cancel button is clicked

    expect(jest.fn()).toHaveBeenCalled()

    Expected number of calls: >= 1
    Received number of calls:    0

      48 |         fireEvent.click(cancelButton);
      49 |
    > 50 |         expect(handleCancel).toHaveBeenCalled();
         |                              ^
      51 |     });
      52 | });
      53 |

问题原因与修复

核心问题

对比组件内的handleDownload和handleCancel函数:

  • handleDownload中明确调用了props.onDownload(),之后才执行隐藏模态框的逻辑
  • handleCancel中只执行了hideChooseLanguageModal(),完全没有调用传入的props.onCancel回调

这就是测试断言失败的根本原因——你在测试中传入的handleCancel mock函数从未被触发。

修复代码

修改组件内的handleCancel函数,添加对props.onCancel的调用:

const handleCancel = async () => {
    props.onCancel?.(); // 调用传入的cancel回调,可选链避免无此参数时报错
    await hideChooseLanguageModal();
};

测试补充说明

因为handleCancel是异步函数,点击事件触发后需要等待异步操作完成,建议在测试中用waitFor包裹断言,确保断言时机正确:

test('should call onCancel when cancel button is clicked', async () => {
    await act(async () => {
        render(
            <ChooseLanguageModal
                languageList={languageList}
                onDownloadLanguage={onDownloadLanguage}
                onDownload={handleDownload}
                onCancel={handleCancel}
            />
        );
    });

    const cancelButton = screen.getByText('Cancel');
    fireEvent.click(cancelButton);

    await waitFor(() => {
        expect(handleCancel).toHaveBeenCalled();
    });
});

内容的提问来源于stack exchange,提问作者Greg Norkett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 06:22:15