Jest测试中React组件onBlur配合useState Hook不生效如何解决?
问题根因
- Enzyme的shallow渲染对React Hook的支持存在兼容性缺陷,无法正确处理连续状态更新下的闭包值捕获。React的状态更新是批量异步执行的,你在同一个执行栈内先调用onChange触发setCardNumber,此时组件还没有重渲染,handleOnBlur函数闭包绑定的还是初始渲染时的cardNumber值(空字符串),所以调用setCardType的时候传入的是空值,本质上setCardType已经执行,只是传入的值不符合预期。
- act的使用方式错误:你这里的状态更新都是同步逻辑,不需要用async/await包裹act,多余的异步逻辑反而会影响Enzyme的状态同步。
- 连续事件调用没有拆分渲染周期:onChange触发后必须等组件完成重渲染,再获取最新的input实例调用onBlur,才能拿到更新后的cardNumber值。
修复方案
方案1:更换为React官方推荐的@testing-library/react(更推荐)
该测试库天然支持Hook的状态更新,更贴近用户实际使用场景,修正后的测试代码如下:
import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { Test } from '.'; describe('Sandbox Test', () => { it('should set cardNumber when doing onChange', () => { render(<Test />); const input = screen.getByRole('textbox'); fireEvent.change(input, { target: { value: '123456' } }); expect(screen.getByText('CardNumber: 123456')).toBeInTheDocument(); expect(screen.getByText('CardType: ')).toBeInTheDocument(); }); it('should set cardType when doing onBlur', () => { render(<Test />); const input = screen.getByRole('textbox'); fireEvent.change(input, { target: { value: '123456' } }); fireEvent.blur(input); expect(screen.getByText('CardNumber: 123456')).toBeInTheDocument(); expect(screen.getByText('CardType: 123456')).toBeInTheDocument(); }); });
方案2:保留Enzyme写法,修改为mount渲染并拆分事件调用
如果必须使用Enzyme,将shallow改为mount(完整渲染),并且拆分两次事件的执行周期,每次更新后重新获取元素实例:
import React from 'react'; import { mount } from 'enzyme'; import { Test } from '.'; import { act } from 'react-dom/test-utils'; describe('Sandbox Test', () => { it('should set cardNumber when doing onChange', () => { const component = mount(<Test />); act(() => { component.find('input').props().onChange({ target: { value: '123456' } }); }); component.update(); expect(component.find('#cn').text()).toEqual(`CardNumber: 123456`); expect(component.find('#ct').text()).toEqual(`CardType: `); }); it('should set cardType when doing onBlur', () => { const component = mount(<Test />); // 先执行change,更新状态重渲染 act(() => { component.find('input').props().onChange({ target: { value: '123456' } }); }); component.update(); // 重渲染后再获取最新的input实例调用blur,此时闭包绑定的是最新的cardNumber act(() => { component.find('input').props().onBlur(); }); component.update(); expect(component.find('#cn').text()).toEqual(`CardNumber: 123456`); expect(component.find('#ct').text()).toEqual(`CardType: 123456`); }); });
内容的提问来源于stack exchange,提问作者Klaha
相关产品推荐
相关产品推荐

