为何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
相关产品推荐
相关产品推荐

