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

如何用React Testing Library测试useState变化时的组件渲染?

问题

我是单元测试新手,目前在React-TypeScript项目中需对组件进行单元测试。我的应用会根据useState的value1值变化,通过条件判断向ControllerComponent传递不同props并渲染对应内容。

组件代码(app.tsx)

import ControllerComponent from './controller-component/controller-component';
export interface ControllerWidgetProps {
  title?: string,
  ....
}
export function ControllerWidget(
  props: ControllerWidgetProps
) {
const [value1,setValue1]=useState(-1);
...

return (
    <Card>        
     <Typography >         
         {props.title}
       </Typography>
      <Grid container spacing={1} pt={0} pb={0} sx={{marginTop:-2,marginLeft:-2}}>      
      <Grid  item xs={12} md={2}>      
      <Box>             
       {(value1 == -1)? (
               <ControllerComponent name="xyz"  image="group 55.png"  /> ) : (value1 == 1 ) ? (
              <ControllerComponent name="abc"  image="group 56.png" />):( value1 == 2)) ?(
              <ControllerComponent name="pqr"  image="group 57.png"/>):(<></>)}
             </>         
))}

export default UiControllerWidget;

现有测试代码

describe('UiControllerWidget', () => {
  it('should render successfully', () => {
  const { baseElement } = render(
    <Provider store={store} >  <UiControllerWidget title={''} />  </Provider>);
  expect(baseElement).toBeTruthy();
});

});

我需要测试当value1变化时,是否传递了预期props并渲染正确组件。


解决方案

方法1:模拟useState控制内部状态

组件的value1是内部状态,可通过jest.spyOn模拟React的useState方法,直接指定value1的取值,再验证渲染结果。

步骤:

  1. 在测试文件顶部导入依赖:
import React from 'react';
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import UiControllerWidget from './app';
import ControllerComponent from './controller-component/controller-component';
import store from './path-to-your-store';
  1. 模拟ControllerComponent(方便检查props传递):
jest.mock('./controller-component/controller-component', () => 
  jest.fn((props) => <div data-testid="controller" {...props} />)
);
  1. 编写测试用例:
describe('UiControllerWidget', () => {
  beforeEach(() => {
    jest.clearAllMocks(); // 清除mock调用记录,保证用例独立
  });

  it('renders ControllerComponent with name "xyz" when value1 is -1', () => {
    // 模拟useState返回value1=-1
    jest.spyOn(React, 'useState').mockReturnValue([-1, jest.fn()]);
    
    render(
      <Provider store={store}>
        <UiControllerWidget title="" />
      </Provider>
    );

    // 验证组件渲染且props正确
    expect(ControllerComponent).toHaveBeenCalledWith(
      expect.objectContaining({
        name: 'xyz',
        image: 'group 55.png'
      }),
      expect.anything() // 忽略React内部传递的ref参数
    );
  });

  it('renders ControllerComponent with name "abc" when value1 is 1', () => {
    jest.spyOn(React, 'useState').mockReturnValue([1, jest.fn()]);
    
    render(
      <Provider store={store}>
        <UiControllerWidget title="" />
      </Provider>
    );

    expect(ControllerComponent).toHaveBeenCalledWith(
      expect.objectContaining({
        name: 'abc',
        image: 'group 56.png'
      }),
      expect.anything()
    );
  });

  it('renders nothing when value1 is not -1/1/2', () => {
    jest.spyOn(React, 'useState').mockReturnValue([3, jest.fn()]);
    
    render(
      <Provider store={store}>
        <UiControllerWidget title="" />
      </Provider>
    );

    // 验证ControllerComponent未被调用
    expect(ControllerComponent).not.toHaveBeenCalled();
    // 或验证页面中无该组件
    expect(screen.queryByTestId('controller')).not.toBeInTheDocument();
  });
});

方法2:重构组件为受控模式(推荐)

将value1改为可传入的props,让组件变为受控组件,测试时直接传入不同值即可,无需模拟内部状态,代码更易维护。

步骤:

  1. 修改组件代码,支持传入value1 props:
export interface ControllerWidgetProps {
  title?: string;
  value1?: number; // 新增props
  ...
}

export function ControllerWidget(props: ControllerWidgetProps) {
  // 使用传入的value1,无则用默认值-1
  const [internalValue1, setValue1] = useState(props.value1 ?? -1);
  // 若完全受控,可直接用props.value1:const value1 = props.value1 ?? -1;
  ...
}
  1. 编写测试用例:
describe('UiControllerWidget', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('renders correct ControllerComponent when value1 is -1', () => {
    render(
      <Provider store={store}>
        <UiControllerWidget title="" value1={-1} />
      </Provider>
    );

    expect(ControllerComponent).toHaveBeenCalledWith(
      expect.objectContaining({ name: 'xyz', image: 'group 55.png' }),
      expect.anything()
    );
  });

  it('renders correct ControllerComponent when value1 is 2', () => {
    render(
      <Provider store={store}>
        <UiControllerWidget title="" value1={2} />
      </Provider>
    );

    expect(ControllerComponent).toHaveBeenCalledWith(
      expect.objectContaining({ name: 'pqr', image: 'group 57.png' }),
      expect.anything()
    );
  });
});

额外场景测试:交互触发状态变化

若组件内有触发setValue1的交互(如按钮点击),可直接测试交互后的状态变化:

it('switches to "abc" component after triggering value change', () => {
  render(
    <Provider store={store}>
      <UiControllerWidget title="" />
    </Provider>
  );

  // 假设存在一个按钮触发setValue1(1)
  fireEvent.click(screen.getByText('切换到1'));

  expect(ControllerComponent).toHaveBeenCalledWith(
    expect.objectContaining({ name: 'abc' }),
    expect.anything()
  );
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 16:46:14