如何用Jest、RTL测试Ionic React组件的输入状态更新与表单提交
注意:你的组件代码中
IonInput的value属性硬编码为了"sss",需要修改为value={username}才能成为正常的受控组件,否则输入内容不会同步到视图,也会影响测试结果。
表单提交测试适配方案
1. 前置依赖Mock
提交逻辑依赖外部SDK服务和路由跳转能力,测试前需要先Mock这两个依赖,避免调用真实接口产生副作用:
// 测试文件顶部添加Mock逻辑 import { Service } from 'src/libs/sdk' import { useHistory } from 'react-router-dom' jest.mock('src/libs/sdk', () => ({ Service: { loginControllerResetPassword: jest.fn() } })) jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: jest.fn() })) // 每个测试用例执行前重置Mock状态 beforeEach(() => { jest.clearAllMocks() ;(useHistory as jest.Mock).mockReturnValue({ push: jest.fn() }) })
2. 完整提交逻辑测试用例
覆盖成功、失败两种核心场景,需要用到waitFor等待异步状态更新完成:
提交成功场景
const TEST_EMAIL = 'user@example.com' test('提交成功时调用重置密码服务并显示模态框', async () => { // 模拟接口返回成功 (Service.loginControllerResetPassword as jest.Mock).mockResolvedValue({ code: 200 }) const { getByTestId, getByText } = render(<PasswordReset />) // 输入邮箱 const input = getByTestId('input-field') fireEvent.ionInput(input, TEST_EMAIL) // 触发提交 fireEvent.click(getByTestId('password-reset-button')) // 验证服务被正确调用 expect(Service.loginControllerResetPassword).toHaveBeenCalledWith({ username: TEST_EMAIL }) // 等待异步操作完成,验证模态框显示 await waitFor(() => { expect(getByText('Ok')).toBeInTheDocument() }) })
提交失败场景
test('提交失败时显示错误提示', async () => { // 模拟接口返回错误 const mockError = new Error('邮箱账号不存在') (Service.loginControllerResetPassword as jest.Mock).mockRejectedValue(mockError) const { getByTestId } = render(<PasswordReset />) fireEvent.ionInput(getByTestId('input-field'), 'wrong@example.com') fireEvent.click(getByTestId('password-reset-button')) // 验证错误信息渲染 await waitFor(() => { expect(getByTestId('reset-error')).toHaveTextContent(mockError.message) }) })
状态更新逻辑覆盖方案
你需要覆盖的handleUsernameInput函数不需要单独测试内部状态,通过行为验证即可实现覆盖率达标:
test('输入内容时同步更新状态', () => { const { getByTestId } = render(<PasswordReset />) const input = getByTestId('input-field') const testValue = 'test@demo.com' fireEvent.ionInput(input, testValue) // 验证输入框值与输入内容一致,即可证明状态更新逻辑生效 expect(input).toHaveValue(testValue) })
如果要覆盖dismissModal函数,补充如下用例:
test('模态框确认后跳转至登录页', async () => { const mockPush = jest.fn() ;(useHistory as jest.Mock).mockReturnValue({ push: mockPush }) ;(Service.loginControllerResetPassword as jest.Mock).mockResolvedValue({}) const { getByTestId, getByText } = render(<PasswordReset />) fireEvent.ionInput(getByTestId('input-field'), TEST_EMAIL) fireEvent.click(getByTestId('password-reset-button')) await waitFor(() => getByText('Ok')) fireEvent.click(getByText('Ok')) expect(mockPush).toHaveBeenCalledWith('/login') })
代码覆盖率统计配置
配置项
在项目根目录的jest.config.js或者package.json的jest配置段添加如下配置:
{ "collectCoverageFrom": [ "src/**/*.{ts,tsx}", "!src/**/*.d.ts", "!src/**/index.ts" ], "coverageReporters": ["text", "html", "lcov"], "coverageThreshold": { "global": { "statements": 90, "branches": 80, "functions": 90, "lines": 90 } } }
运行命令
执行测试时添加--coverage参数即可生成覆盖率报告:
npm run test -- --coverage
运行完成后会在控制台输出覆盖率统计,同时生成coverage目录,打开coverage/lcov-report/index.html可以查看每行代码的覆盖情况。
内容的提问来源于stack exchange,提问作者Ahmed J.
相关产品推荐
相关产品推荐

