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

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
修复方案

问题根源

  1. 违背受控组件原则:直接操作DOM的inputRef.current.value修改输入值再同步状态,会导致React状态与DOM值异步不同步,测试环境下这个问题被放大。
  2. 测试未等待状态更新: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 19:16:09