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

如何测试useEffect中触发的alert弹窗?

问题:测试Redux Store中counter值达到20时触发的Alert弹窗

我需要验证当Redux Store里的counter值变为20时,页面会弹出对应的alert提示。目前通过useEffect监听counter状态,满足条件时调用alert,但测试用例无法触发预期的alert行为。以下是我的App组件代码和测试用例:

App组件代码

import React, { useEffect, useState } from 'react';
import { configureStore, createAction } from '@reduxjs/toolkit';
import { AnyAction, combineReducers, Dispatch } from 'redux';
import { useDispatch, useSelector } from 'react-redux';

export const increment = createAction<number>('increment');
export const decrement = createAction<number>('decrement');

export const counter = (state = 0, action: AnyAction) =>
  increment.match(action)
    ? state + action.payload
    : decrement.match(action)
    ? state - action.payload
    : state;

const root = combineReducers({ counter });

const timer = ({
  dispatch,
  getState,
}: {
  dispatch: Dispatch;
  getState: any;
}) => {
  setInterval(() => dispatch(increment(1)), 1000);

  return (next: Dispatch) => (action: AnyAction) => {
    const store = getState();
    if (store.counter < 0) {
      next(decrement(1));
    } else if (store.counter < 21 || !increment.match(action)) {
      next(action);
    }
  };
};

export const store = configureStore({
  reducer: root,
  middleware: [timer],
});

/* const handleStateChange = () => {
  const state = store.getState();
  if (state.counter === 20) {
    alert('Your counter reached the value 20!');
  }
}

store.subscribe(handleStateChange) */

const App = () => {
  const [step, setStep] = useState<number>(1);
  const dispatch = useDispatch();
  const counter = useSelector((state: Record<string, number>) => state.counter);

  const handleChange = (value: string) => {
    const convertedValue = Number(value);
    setStep(convertedValue);
  };

  useEffect(() => {
    if (counter === 20) {
      alert('Your counter reached the value 20!')
    }
  }, [counter]);

  useEffect(() => {
    const timeStamp = new Date();
    console.log('Did Mount: ', timeStamp);

    return () => console.log('Did Unmount ', new Date());
  }, []);

  return (
    <>
      <button onClick={() => dispatch(decrement(step))}>-</button>
      {counter}
      <button onClick={() => dispatch(increment(step))}>+</button>
      <div>
        Step equal to:
        <select
          value={step}
          onChange={(value) => handleChange(value.target.value)}
        >
          <option>1</option>
          <option>2</option>
          <option>3</option>
        </select>
      </div>
    </>
  );
};

export default App;

现有测试用例

it('Should trigger alert when reach 20', () => {
    const previousState = 19;
    const store = mockStore(previousState);
    const alert = jest.spyOn(window, 'alert').mockImplementation();
    render(
      <Provider store={store}>
        <App />
      </Provider>
    );

    userEvent.click(screen.getByText('+'));
    expect(alert).toHaveBeenCalled();
  });

问题分析与修复方案

1. 核心问题:mockStore的局限性

你使用的redux-mock-store仅模拟了store的dispatch逻辑,不会实际执行reducer来更新状态。点击+按钮后,store的counter值并不会从19变为20,因此监听counter的useEffect不会触发,alert自然不会被调用。

2. 修复后的测试用例

直接使用你定义的真实Redux配置(或测试专用的store),同时处理定时器避免干扰测试:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Provider } from 'react-redux';
import App, { store, increment } from './App';

it('Should trigger alert when counter reaches 20', async () => {
  // Mock window.alert
  const alertSpy = jest.spyOn(window, 'alert').mockImplementation(() => {});

  // 提前将counter设置为19
  store.dispatch(increment(19));

  render(
    <Provider store={store}>
      <App />
    </Provider>
  );

  // 点击+按钮,使counter变为20
  await userEvent.click(screen.getByText('+'));

  // 验证alert被正确调用
  expect(alertSpy).toHaveBeenCalledWith('Your counter reached the value 20!');

  // 清理:清除定时器和mock
  jest.clearAllTimers();
  alertSpy.mockRestore();
});

3. 优化建议:避免定时器干扰测试

原代码中的timer中间件会每秒自动触发increment,可能导致测试过程中counter值意外变化。可以创建一个不含该中间件的测试专用store:

// 引入必要的依赖
import { configureStore } from '@reduxjs/toolkit';
import { root } from './App';

// 创建测试专用store,移除timer中间件
const testStore = configureStore({
  reducer: root,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
});

然后在测试中使用testStore替代原store,确保测试环境不受自动定时器影响。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 20:01:02