我正在处理一个项目,并试图对输入字段的简单状态更改进行单元测试.在try 使用Reaction测试库userEvent.setup()方法模拟onChange事件时,我总是得到一个失败的断言.

测试:

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;

    // Check initial value
    expect(nameInput.value).toEqual('');

    // Simulate user typing
    await user.type(nameInput, 'Johnny Bravo');

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

    // Verify updated value after change event (FAILING HERE)
    //expect(nameInput.value).toEqual('Johnny Bravo');
  });
});

相关组件:

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| });

使用React 18.json相关包deps:

    "@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"

这是输入的console.log在"写入"之后的输出:

<ref *1> HTMLInputElement {
  '__reactFiber$ryal2e46zro': FiberNode {
    tag: 5,
    key: null,
    elementType: 'input',
    type: 'input',
    stateNode: [Circular *1],
    return: FiberNode {
      tag: 5,
      key: null,
      elementType: 'div',
      type: 'div',
      stateNode: [HTMLDivElement],
      return: [FiberNode],
      child: [FiberNode],
      sibling: null,
      index: 0,
      ref: null,
      pendingProps: [Object],
      memoizedProps: [Object],
      updateQueue: null,
      memoizedState: null,
      dependencies: null,
      mode: 1,
      flags: 0,
      subtreeFlags: 1048576,
      deletions: null,
      lanes: 0,
      childLanes: 0,
      alternate: null,
      actualDuration: 0,
      actualStartTime: -1,
      selfBaseDuration: 0,
      treeBaseDuration: 0,
      _debugSource: [Object],
      _debugOwner: [FiberNode],
      _debugNeedsRemount: false,
      _debugHookTypes: null
    },
    child: null,
    sibling: null,
    index: 1,
    ref: null,
    pendingProps: {
      id: 'clientName',
      type: 'text',
      value: '',
      onChange: [Function: onChange],
      className: 'form-control mb-2',
      'data-testid': 'client-name'
    },
    memoizedProps: {
      id: 'clientName',
      type: 'text',
      value: '',
      onChange: [Function: onChange],
      className: 'form-control mb-2',
      'data-testid': 'client-name'
    },
    updateQueue: null,
    memoizedState: null,
    dependencies: null,
    mode: 1,
    flags: 1048576,
    subtreeFlags: 0,
    deletions: null,
    lanes: 0,
    childLanes: 0,
    alternate: null,
    actualDuration: 0,
    actualStartTime: -1,
    selfBaseDuration: 0,
    treeBaseDuration: 0,
    _debugSource: {
      fileName: '/home/sam/code/react_pro/mern_project_mgmt/client/src/components/common/LabelWithInput.tsx',
      lineNumber: 12,
      columnNumber: 7
    },
    _debugOwner: FiberNode {
      tag: 0,
      key: null,
      elementType: [Function: LabelWithInput],
      type: [Function: LabelWithInput],
      stateNode: null,
      return: [FiberNode],
      child: [FiberNode],
      sibling: [FiberNode],
      index: 0,
      ref: null,
      pendingProps: [Object],
      memoizedProps: [Object],
      updateQueue: null,
      memoizedState: null,
      dependencies: null,
      mode: 1,
      flags: 1048577,
      subtreeFlags: 1048576,
      deletions: null,
      lanes: 0,
      childLanes: 0,
      alternate: null,
      actualDuration: 0,
      actualStartTime: -1,
      selfBaseDuration: 0,
      treeBaseDuration: 0,
      _debugSource: [Object],
      _debugOwner: [FiberNode],
      _debugNeedsRemount: false,
      _debugHookTypes: null
    },
    _debugNeedsRemount: false,
    _debugHookTypes: null
  },
  '__reactProps$ryal2e46zro': {
    id: 'clientName',
    type: 'text',
    value: '',
    onChange: [Function: onChange],
    className: 'form-control mb-2',
    'data-testid': 'client-name'
  },
  _wrapperState: { initialChecked: undefined, initialValue: '', controlled: true },
  '__reactEvents$ryal2e46zro': Set(1) { 'invalid__bubble' },
  value: [Getter/Setter],
  _valueTracker: {
    getValue: [Function: getValue],
    setValue: [Function: setValue],
    stopTracking: [Function: stopTracking]
  },
  setSelectionRange: [Function: intercept] {
    [Symbol(Interceptor for programmatical calls)]: Symbol(Interceptor for programmatical calls)
  },
  selectionStart: [Getter/Setter],
  selectionEnd: [Getter/Setter],
  select: [Function: intercept] {
    [Symbol(Interceptor for programmatical calls)]: Symbol(Interceptor for programmatical calls)
  },
  setRangeText: [Function: intercept] {
    [Symbol(Interceptor for programmatical calls)]: Symbol(Interceptor for programmatical calls)
  },
  [Symbol(Last check for pointer-events)]: {
    '1': {},
    '2': {},
    result: { pointerEvents: 'auto', tree: [Array] }
  },
  [Symbol(Displayed selection in UI)]: { anchorOffset: 0, focusOffset: 0 },
  [Symbol(Node prepared with document state workarounds)]: Symbol(Node prepared with document state workarounds),
  [Symbol(Initial value to compare on blur)]: '',
  [Symbol(Displayed value in UI)]: undefined,
  [Symbol(Track programmatic changes for React workaround)]: undefined
}

如你所见,value的财产仍然是"......所以也许await user.type()号公路根本不管用

我只想断言,当用户输入某些内容时,输入值会正确地更新.

为什么这个不能通过?

谢谢!


感谢@大卫团的回答,更新了W/答案. 最终测试文件编码:

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';

// Create a mock Parent component:
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;

    // Check initial value
    expect(nameInput.value).toEqual('');

    // Simulate user typing
    await user.type(nameInput, 'Johnny Bravo');

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

    // Verify updated value after change event
    expect(nameInput.value).toEqual('Johnny Bravo');
  });
});

推荐答案

只是猜测,但看起来...

  1. 在这部分中
screen.getByRole('textbox', {
  name: 'Client Name:',
}

我不知道您的CreateClientFormLabelWithInput中的输入框是否为role=textbox,所以实际上找不到该角色的元素可能是Jest 的.然而,事实上JEST可以断言字段是空的,那么它可能工作得很好.如果下一个还是失败了,一定要试一试.说到下一个...

  1. 在您的beforeEach块中,您将模拟的props mockProps传递给CreateClientForm组件,使这setClient...个组件变得毫无用处,因为它是一个模拟/空函数,当您测试输入字段的类型时.

(更新版)

在这种情况下,或者创建一个测试组件,该组件与父组件CreateClientForm具有相同的useState--

const TestComponent = () => {
  const [clientName, setClientName] = useState('')
  const [clientEmail, setClientEmail] = useState('')
  const [clientPhone, setClientPhone] = useState('')
  const handleSubmit = () => {
    return
  }

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

所以在你的测试中,

it('updates state values as a user types', async () => {
  render(<TestComponent />)
  //... rest of the code

干杯.

Reactjs相关问答推荐

全局上下文挂钩-替代不起作用

如何使用React防止并发注册并处理Firestore数据库中的插槽可用性

无法覆盖MUI工具栏上的左右填充,除非使用!重要

在一个地方调用函数会影响其他地方调用该函数

props 可以在Reaction中锻造吗?

Formik验证不适用于物料UI自动完成

根据另一个Select中的选定值更改Select中的值

两条react 路由的行为不同.有没有人能解释一下,我已经找了好几天了

为多租户SSO登录配置Azure AD应用程序

当我try 部署我的Reaction应用程序时,为什么在此PrevState语句中收到语法错误?

使用Ionic 7和React 18,如何访问历史对象以在页面之间导航?

无法使用导入app/page.jsx的组件中的tailwind 类文本操作和自定义动画

有没有办法在 React Router v6 中的路由匹配上运行一些逻辑/功能?

为什么我的 Next.js (React) 组件只有在页面中时才有效?

使用 map 循环浏览列表列表中的列表并显示它们

将 useState 设置为组件内部的值

单元测试 useLoaderData() react-router v6 加载器函数

Cypress - 替换 React 项目中的变量

如何使用react 路由将 url 参数限制为一组特定的值?

我想将悬停 colored颜色 更改为红色.写了一个话题,这个元素没有react ,怎么解决呢?