如何测试React Native中依赖useEffect异步调用更新UI的OrdersScreen组件
如何测试React Native中依赖useEffect异步调用更新UI的OrdersScreen组件
我来帮你梳理下这个测试的问题,一步步解决它:
首先看你遇到的错误,大概率是Redux的测试环境没配置对——比如你的测试store里没注册ordersReducer,导致dispatch(addOrder(ord))执行出错,进而阻塞了后续的setRenderLoader(false)调用。另外你的测试逻辑还有几个不合理的地方,比如手动mock组件内部的setRenderLoader,这完全没关联到真实的组件状态,断言自然无效。
下面是具体的修复方案:
1. 先确保Redux测试环境配置正确
你的renderWithRedux工具函数必须确保测试用的store已经注册了ordersReducer,不然addOrder这个action会因为找不到对应reducer而抛出错误,这就是你看到报错指向setRenderLoader(false)的原因(前面的dispatch出错,代码没走到这一步或者抛出了未捕获的错误)。
如果用mock store,可以用redux-mock-store库;如果用真实store,要正确组合reducer后传入Provider。
2. 不要mock组件内部的state setter,通过渲染结果断言
组件内部的setRenderLoader是私有状态,测试不应该直接干预它,而是通过组件渲染的内容来判断状态变化:
- 初始加载时,应该渲染
<Loader /> - API调用成功后,Loader消失,渲染
<SectionList />
3. 正确处理异步操作的测试流程
用act包裹所有会触发React状态更新的逻辑,包括组件渲染和异步操作的等待,确保测试能捕获到所有状态更新。
完整的测试代码示例
import renderer from 'react-test-renderer'; import { act } from 'react-test-renderer'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import OrdersScreen from './OrdersScreen'; import ordersController from './path-to-ordersController'; import { addOrder } from './path-to-actions'; import Loader from './path-to-Loader'; import SectionList from 'react-native'; // 初始化mock store const mockStore = configureStore([]); describe('<OrdersScreen/>', () => { test('成功加载订单后渲染SectionList', async () => { // 1. Mock API返回测试数据 const TEST_ORDERS = [ { id: 1, title: '测试订单1' }, { id: 2, title: '测试订单2' } ]; ordersController.getOrdersForAgent = jest.fn(() => Promise.resolve(TEST_ORDERS)); // 2. 创建测试用Redux Store const store = mockStore({ ordersReducer: [] }); let component; // 3. 用act包裹渲染,确保捕获初始状态更新 await act(async () => { component = renderer.create( <Provider store={store}> <OrdersScreen navigation={{}} /> </Provider> ); // 等待异步API调用和状态更新完成 await Promise.resolve(); }); // 4. 断言初始状态:Loader存在 let root = component.root; expect(root.findByType(Loader)).toBeTruthy(); // 5. 等待异步更新完成后,重新获取组件根节点 await act(async () => { await Promise.resolve(); }); root = component.root; // 6. 断言API成功后的状态 expect(() => root.findByType(Loader)).toThrow(); // Loader已消失 expect(root.findByType(SectionList)).toBeTruthy(); // SectionList已渲染 // 7. 断言Redux action是否正确分发 const dispatchedActions = store.getActions(); expect(dispatchedActions).toEqual(TEST_ORDERS.map(order => addOrder(order))); expect(store.getState().ordersReducer.length).toBe(TEST_ORDERS.length); }); test('API调用失败时的错误处理', async () => { // Mock API失败场景 ordersController.getOrdersForAgent = jest.fn(() => Promise.reject(new Error('请求失败'))); const store = mockStore({ ordersReducer: [] }); let component; await act(async () => { component = renderer.create( <Provider store={store}> <OrdersScreen navigation={{}} /> </Provider> ); await Promise.resolve(); }); const root = component.root; // 根据你的业务逻辑断言:比如Loader是否消失、是否显示错误提示 expect(() => root.findByType(Loader)).toThrow(); // 如果有错误提示组件:expect(root.findByType(ErrorTip)).toBeTruthy(); }); });
额外注意事项
- 每次组件状态更新后,要重新获取
component.root,因为组件树已经发生了变化 - 测试要关注组件的输入输出:比如API返回、props → 渲染结果、Redux状态,不要去mock组件内部的私有方法或state setter
- 如果你的
renderWithRedux已经封装了Provider和store,直接用它替换示例中的Provider部分即可
备注:内容来源于stack exchange,提问作者cooltogo
相关产品推荐
相关产品推荐

