如何测试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
相关产品推荐
相关产品推荐

