React测试中点击按钮无法正确更新输入框值的问题求助
问题描述
开发了一个React组件,点击按钮会向输入框追加字符"1"。该功能在开发服务器中正常工作,但运行测试代码时,输入框的值长度始终少于按钮点击次数,导致测试失败。期望每次点击按钮后输入值依次为""→"1"→"11"→"111"等,请问如何修复该错误以通过测试?
我的应用代码
export function App() { const [inputValue, setInputValue] = React.useState(""); const inputRef = React.useRef<HTMLInputElement>(null); const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault(); if (inputRef.current) { inputRef.current.value = inputRef.current.value + "1"; setInputValue(inputRef.current.value); } }; return ( <form> <label htmlFor="password-input" > 비밀번호 </label> <input ref={inputRef} type="password" id="password-input" name="password-input" value={inputValue} /> <button className="insert" data-testId={1} onClick={(e) => { handleClick(e) }}> 입력 </button> </form>) }
我的测试代码
test("Does clicking the button change the input value?", async () => { render(<App />); userEvent.click(await screen.findByTestId(1)); userEvent.click(await screen.findByTestId(1)); expect((await screen.findByLabelText<HTMLInputElement>(`비밀번호`)).value).toHaveLength(2); userEvent.click(await screen.findByTestId(1)); userEvent.click(await screen.findByTestId(1)); userEvent.click(await screen.findByTestId(1)); userEvent.click(await screen.findByTestId(1)); expect((await screen.findByLabelText<HTMLInputElement>(`비밀번호`)).value).toHaveLength(6); })
测试运行结果
expect(received).toHaveLength(expected) Expected length: 2 Received length: 1 Received string: "1" 37 | userEvent.click(await screen.findByTestId(1)); 38 | > 39 | expect((await screen.findByLabelText<HTMLInputElement>(`비밀번호`)).value).toHaveLength(2); | ^ 40 | 41 | userEvent.click(await screen.findByTestId(1)); 42 | userEvent.click(await screen.findByTestId(1)); at Object.toHaveLength (src/test/my.spec.tsx:39:80) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total
修复方案
问题根源
- 违背受控组件原则:直接操作DOM的
inputRef.current.value修改输入值再同步状态,会导致React状态与DOM值异步不同步,测试环境下这个问题被放大。 - 测试未等待状态更新:
userEvent.click是异步操作,点击后React状态更新需要时间,直接断言会拿到旧状态值。
修改步骤
1. 重构应用代码,使用受控组件模式
移除inputRef,直接基于当前状态更新输入值:
export function App() { const [inputValue, setInputValue] = React.useState(""); const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault(); // 基于前一次状态更新,避免异步状态覆盖 setInputValue(prev => prev + "1"); }; return ( <form> <label htmlFor="password-input"> 비밀번호 </label> <input type="password" id="password-input" name="password-input" value={inputValue} /> <button className="insert" data-testId="add-1-btn" onClick={handleClick}> 입력 </button> </form>) }
2. 调整测试代码,等待状态更新完成
使用waitFor包裹断言,确保状态更新后再检查结果,同时userEvent.click需要await:
test("Does clicking the button change the input value?", async () => { render(<App />); const addBtn = await screen.findByTestId("add-1-btn"); const passwordInput = await screen.findByLabelText<HTMLInputElement>("비밀번호"); // 点击两次 await userEvent.click(addBtn); await userEvent.click(addBtn); await waitFor(() => { expect(passwordInput.value).toHaveLength(2); }); // 再点击四次 await userEvent.click(addBtn); await userEvent.click(addBtn); await userEvent.click(addBtn); await userEvent.click(addBtn); await waitFor(() => { expect(passwordInput.value).toHaveLength(6); }); })
关键说明
- 受控组件模式下,输入值完全由
inputValue状态控制,彻底避免DOM与状态不同步的问题。 waitFor会反复执行断言直到条件满足,确保拿到React状态更新后的最新DOM值。- 将
data-testId改为字符串格式,符合前端测试的常规实践,避免数字类型可能引发的匹配问题。
内容的提问来源于stack exchange,提问作者sunub
相关产品推荐
相关产品推荐

