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

如何使用Jest测试ComponentDidMount生命周期及绑定的各类window事件

测试方案

以下测试基于Jest + Enzyme/React Testing Library实现,分为两部分验证:


1 验证componentDidMount调用及事件绑定正确性

核心逻辑是挂载组件前先监听window.addEventListener调用,验证调用次数、事件名、绑定的处理函数是否符合预期:

import { mount } from 'enzyme';
import MyComponent from './MyComponent'; // 替换为你的组件路径

describe('MyComponent componentDidMount', () => {
  it('should bind 4 specified window events after component mount', () => {
    const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
    const mockProcessFn = jest.fn();
    const wrapper = mount(<MyComponent processAdminMessage={mockProcessFn} />);
    const componentInstance = wrapper.instance();

    // 验证事件绑定完全符合预期
    expect(addEventListenerSpy).toHaveBeenCalledTimes(4);
    expect(addEventListenerSpy).toHaveBeenCalledWith('message', componentInstance.handleMessage);
    expect(addEventListenerSpy).toHaveBeenCalledWith('beforeunload', componentInstance.handleBeforeUnload);
    expect(addEventListenerSpy).toHaveBeenCalledWith('blur', componentInstance.handleAppFocusOut);
    expect(addEventListenerSpy).toHaveBeenCalledWith('focus', componentInstance.handleAppFocusIn);

    // 测试后清理
    addEventListenerSpy.mockRestore();
    wrapper.unmount();
  });
});

2 逐个验证事件处理函数执行逻辑

2.1 handleMessage测试

验证只有收到包含adminToClient字段的消息时,才会调用props传入的处理函数:

it('handleMessage should trigger processAdminMessage only when receive valid data', () => {
  const mockProcessFn = jest.fn();
  const wrapper = mount(<MyComponent processAdminMessage={mockProcessFn} />);
  const componentInstance = wrapper.instance();

  // 合法数据场景
  componentInstance.handleMessage({ data: { adminToClient: 'test_cmd' } });
  expect(mockProcessFn).toHaveBeenCalledWith('test_cmd');

  // 非法数据场景(无adminToClient字段)
  componentInstance.handleMessage({ data: { otherKey: 'test' } });
  // 非法数据场景(无data字段)
  componentInstance.handleMessage({});
  expect(mockProcessFn).toHaveBeenCalledTimes(1); // 仅合法场景触发1次
});

2.2 handleBeforeUnload测试

验证触发时正确写入时间戳到sessionStorage:

it('handleBeforeUnload should save current timestamp to sessionStorage', () => {
  const setItemSpy = jest.spyOn(sessionStorage.__proto__, 'setItem');
  const wrapper = mount(<MyComponent processAdminMessage={jest.fn()} />);
  const componentInstance = wrapper.instance();
  const mockTimestamp = 1690000000000;
  //  mock系统时间
  jest.useFakeTimers().setSystemTime(mockTimestamp);

  componentInstance.handleBeforeUnload();
  expect(setItemSpy).toHaveBeenCalledWith('lastVisit', mockTimestamp.toString());

  // 测试后清理
  jest.useRealTimers();
  setItemSpy.mockRestore();
});

2.3 handleAppFocusOut测试

验证仅当焦点元素非BODY时,才会保存到state:

it('handleAppFocusOut should save non-BODY active element to state', () => {
  const wrapper = mount(<MyComponent processAdminMessage={jest.fn()} />);
  const componentInstance = wrapper.instance();
  // 模拟焦点在input元素上
  const mockInput = document.createElement('input');
  Object.defineProperty(document, 'activeElement', {
    value: mockInput,
    configurable: true
  });

  componentInstance.handleAppFocusOut();
  expect(wrapper.state('lastFocusedField')).toBe(mockInput);

  // 模拟焦点在BODY上
  Object.defineProperty(document, 'activeElement', {
    value: document.body,
    configurable: true
  });
  componentInstance.handleAppFocusOut();
  expect(wrapper.state('lastFocusedField')).toBe(mockInput); // 未修改state
});

2.4 handleAppFocusIn测试

验证有上次聚焦元素时,会自动切回焦点,且最终清空state存储的元素:

it('handleAppFocusIn should focus back saved element if current focus is BODY', () => {
  jest.useFakeTimers();
  const mockInput = document.createElement('input');
  mockInput.focus = jest.fn(); //  mock focus方法
  const wrapper = mount(<MyComponent processAdminMessage={jest.fn()} />, {
    initialState: { lastFocusedField: mockInput }
  });
  const componentInstance = wrapper.instance();
  // 模拟当前焦点在BODY
  Object.defineProperty(document, 'activeElement', {
    value: document.body,
    configurable: true
  });

  componentInstance.handleAppFocusIn();
  // 验证state已清空
  expect(wrapper.state('lastFocusedField')).toBeNull();
  // 执行异步定时器
  jest.runAllTimers();
  // 验证触发了原元素的focus方法
  expect(mockInput.focus).toHaveBeenCalled();

  jest.useRealTimers();
});

补充注意项

如果你的组件有componentWillUnmount生命周期做事件解绑,建议同步添加解绑逻辑的测试,方法和绑定测试类似,监听window.removeEventListener的调用即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 23:48:03