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

React测试问题:simulate点击后PointsList组件props未更新

解决Enzyme测试中函数式组件Props不更新的问题

嘿,刚上手React测试遇到这种卡壳的情况太正常了!我来帮你拆解下问题到底出在哪,然后直接给你能跑通的解决方案。

问题根源分析

你现在的测试有两个核心问题:

  1. 共享组件实例导致的状态污染:你在describe块里一次性挂载了pointsList,所有测试用例共用同一个实例。而且你把simulate('click')写在了describe层级,这会在所有it执行前就触发点击,测试之间完全没有隔离。
  2. 没有手动更新Props:PointsList是纯函数式组件,它的渲染完全依赖传入的props。你触发onPointDelete后,只是调用了mock函数,但没有模拟父组件(App)更新state后传递新props的过程——Enzyme的update()只会让组件重新渲染现有props,不会自动帮你修改props。

修复后的测试代码

直接把你的PointsList.spec.js改成这样:

import React from 'react';
import {mount} from 'enzyme';
import PointsList from './PointsList';

describe('PointsList component', () => {
  // 用beforeEach重置mock和初始props,保证每个测试都是干净的
  let mockOnPointDelete;
  let initialProps;

  beforeEach(() => {
    mockOnPointDelete = jest.fn();
    initialProps = {
      points: [
        { id: 1, name: 'Point 1', coordinates: [55.50, 57.50] },
        { id: 2, name: 'Point 2', coordinates: [55.51, 57.51] }
      ],
      onPointDelete: mockOnPointDelete,
      onDragEnd: () => { }
    };
  });

  describe('when deleting a point', () => {
    it('calls the props.onPointDelete with correct id', () => {
      // 每个测试单独挂载组件,避免实例共享
      const pointsList = mount(<PointsList {...initialProps} />);
      // 注意:你原来用at(1)是第二个点,对应id=2,这里要保持逻辑一致
      pointsList.find('li').at(1).find('.btn.btn-delete').simulate('click');
      
      expect(mockOnPointDelete).toHaveBeenCalledTimes(1);
      expect(mockOnPointDelete).toHaveBeenCalledWith(2);
    });

    it('renders 1 point after deletion', () => {
      const pointsList = mount(<PointsList {...initialProps} />);
      pointsList.find('li').at(1).find('.btn.btn-delete').simulate('click');
      
      // 模拟父组件更新props:删除对应的点
      const updatedPoints = initialProps.points.filter(point => point.id !== 2);
      pointsList.setProps({ points: updatedPoints });
      
      // 让组件重新渲染新的props
      pointsList.update();
      
      expect(pointsList.prop('points')).toHaveLength(1);
      // 额外验证渲染出来的列表项数量是否正确
      expect(pointsList.find('li').length).toBe(1);
    });
  });
})

关键修改点说明

  1. 隔离测试环境:用beforeEach在每个测试前重置mock和初始props,每个it块单独挂载组件,彻底避免测试之间的状态干扰。
  2. 手动更新Props:在第二个测试里,通过setProps()传递删除后的新points数组,模拟真实场景中父组件更新state后传递新props的行为。
  3. 明确测试逻辑:补充验证onPointDelete的调用参数,让测试更严谨。

进阶优化(模拟真实父组件场景)

如果你想更贴近真实的App运行逻辑,可以把PointsList包裹在一个模拟的父组件里,让它自动处理state更新:

import React, { useState } from 'react';

// 模拟App组件的逻辑
const MockParent = () => {
  const [points, setPoints] = useState(initialProps.points);
  const handleDelete = (id) => {
    setPoints(prev => prev.filter(p => p.id !== id));
  };
  return <PointsList points={points} onPointDelete={handleDelete} onDragEnd={() => {}} />;
};

// 测试时挂载MockParent
it('renders 1 point after deletion (parent component simulation)', () => {
  const parentWrapper = mount(<MockParent />);
  parentWrapper.find('li').at(1).find('.btn.btn-delete').simulate('click');
  
  // 父组件state更新后,PointsList的props会自动更新,直接验证即可
  expect(parentWrapper.find(PointsList).prop('points')).toHaveLength(1);
});

这样测试的场景就和真实用户操作完全一致了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:32:00