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

React Testing Library+Vitest单元测试中如何正确更新输入框值?

输入框状态变更单元测试失败问题解决

问题背景

开发项目时,对输入框状态变更做单元测试,使用React Testing Library的userEvent.setup()模拟输入事件后,断言输入框值更新始终失败,输入框value始终为空。

原测试代码

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MockedProvider } from '@apollo/client/testing';
import CreateClientForm from '../../src/components/CreateClientForm';
import { vi } from 'vitest';

const mockProps: CreateClientFormProps = {
  clientName: '',
  setClientName: vi.fn(),
  clientEmail: '',
  setClientEmail: vi.fn(),
  clientPhone: '',
  setClientPhone: vi.fn(),
};

beforeEach(() => {
  render(
    <MockedProvider>
      <CreateClientForm {...mockProps} />
    </MockedProvider>
  );
});

describe('Create Client Form In Modal', () => {
  it('renders the correct initial input values', () => {
    expect(screen.getByLabelText('Client Name:')).toBeInTheDocument();
    expect(screen.getByLabelText('Client Email:')).toBeInTheDocument();
    expect(screen.getByLabelText('Client Phone:')).toBeInTheDocument();
  });

  it('updates state values as a user types', async () => {
    const user = userEvent.setup();

    const nameInput = screen.getByRole('textbox', {
      name: 'Client Name:',
    }) as HTMLInputElement;

    // 检查初始值
    expect(nameInput.value).toEqual('');

    // 模拟用户输入
    await user.type(nameInput, 'Johnny Bravo');

    console.log(
      screen.getByRole('textbox', {
        name: 'Client Name:',
      })
    );

    // 断言更新后的值(此处失败)
    //expect(nameInput.value).toEqual('Johnny Bravo');
  });
});

相关组件代码

CreateClientForm组件

import LabelWithInput from './common/LabelWithInput';

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
};

const CreateClientForm = ({
  clientName,
  setClientName,
  clientEmail,
  setClientEmail,
  clientPhone,
  setClientPhone,
}: CreateClientFormProps) => {
  return (
    <form onSubmit={handleSubmit}>
      <LabelWithInput
        label='Client Name:'
        type='text'
        id='clientName'
        value={clientName}
        onChange={(e) => setClientName(e.target.value)}
        className='form-control mb-2'
        data-testid='client-name'
      />
      <LabelWithInput
        label='Client Email:'
        type='text'
        id='clientEmail'
        value={clientEmail}
        onChange={(e) => setClientEmail(e.target.value)}
        className='form-control mb-2'
        data-testid='client-email'
      />
      <LabelWithInput
        label='Client Phone:'
        type='text'
        id='clientPhone'
        value={clientPhone}
        onChange={(e) => setClientPhone(e.target.value)}
        className='form-control mb-2'
        data-testid='client-phone'
      />
      <button className='btn btn-primary'>Submit</button>
    </form>
  );
};

export default CreateClientForm;

LabelWithInput组件

import { type ComponentPropsWithoutRef } from 'react';

type LabelWithInputProps = {
  id: string;
  label: string;
} & ComponentPropsWithoutRef<'input'>;

const LabelWithInput = ({ id, label, ...props }: LabelWithInputProps) => {
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} {...props} />
    </div>
  );
};

export default LabelWithInput;

终端错误信息

FAIL  tests/components/CreateClientForm.test.tsx > Create Client Form In Modal > updates state values as a user types
AssertionError: expected '' to deeply equal 'Johnny Bravo'

- Expected
+ Received

- Johnny Bravo

 ❯ tests/components/CreateClientForm.test.tsx:51:29
     49| 
     50|     // Verify updated value after change event
     51|     expect(nameInput.value).toEqual('Johnny Bravo');
       |                             ^
     52|   });
     53| });

依赖版本

"@testing-library/jest-dom": "^6.1.4",
    "@testing-library/react": "^14.0.0",
    "@testing-library/user-event": "^14.5.1",
    "@types/jest": "^29.5.8",
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@vitejs/plugin-react": "^4.0.3",
    "dom-testing-library": "^5.0.0",
    "eslint": "^8.45.0",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "jsdom": "^22.1.0",
    "ts-node": "^10.9.1",
    "vite": "^4.4.5",
    "vitest": "^0.34.6"

问题原因

输入框是受控组件,value绑定的是props传入的clientName等状态值。原测试中使用vi.fn()模拟的setClientName等函数仅能记录调用情况,不会实际更新对应的状态值。因此即使触发了onChange事件,clientName始终为空,输入框值也不会变化,导致断言失败。

解决方案

创建一个模拟父组件,使用React的useState管理真实的状态,将状态和对应的setter函数传入CreateClientForm。这样触发输入事件时,setter会更新状态,输入框的value也会同步更新,断言即可通过。

最终测试代码

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MockedProvider } from '@apollo/client/testing';
import CreateClientForm from '../../src/components/CreateClientForm';
import { useState } from 'react';

// 创建模拟父组件管理状态
const MockParentComponent = () => {
  const [clientName, setClientName] = useState('');
  const [clientEmail, setClientEmail] = useState('');
  const [clientPhone, setClientPhone] = useState('');

  return (
    <MockedProvider>
      <CreateClientForm
        clientName={clientName}
        setClientName={setClientName}
        clientEmail={clientEmail}
        setClientEmail={setClientEmail}
        clientPhone={clientPhone}
        setClientPhone={setClientPhone}
      />
    </MockedProvider>
  );
};

beforeEach(() => {
  render(
    <MockedProvider>
      <MockParentComponent />
    </MockedProvider>
  );
});

describe('Create Client Form In Modal', () => {
  it('renders the correct initial input values', () => {
    expect(screen.getByLabelText('Client Name:')).toBeInTheDocument();
    expect(screen.getByLabelText('Client Email:')).toBeInTheDocument();
    expect(screen.getByLabelText('Client Phone:')).toBeInTheDocument();
  });

  it('updates state values as a user types', async () => {
    const user = userEvent.setup();

    const nameInput = screen.getByRole('textbox', {
      name: 'Client Name:',
    }) as HTMLInputElement;

    // 检查初始值
    expect(nameInput.value).toEqual('');

    // 模拟用户输入
    await user.type(nameInput, 'Johnny Bravo');

    console.log(
      screen.getByRole('textbox', {
        name: 'Client Name:',
      })
    );

    // 断言更新后的值
    expect(nameInput.value).toEqual('Johnny Bravo');
  });
});

内容的提问来源于stack exchange,提问作者CodeConnoisseur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 23:08:12