如何在Jest中Mock React Native组件的items状态以完成测试?
问题描述
我有一个React Native 0.70组件,通过FlatList展示内部状态items数组,该数组在useEffect钩子中完成赋值。现在想用Jest 29测试,验证item名称是否显示在屏幕上,需要Mock items状态,但当前测试里两个断言失败,item未渲染出来。
FlatList的renderItem代码
return ( <TouchableOpacity testID={'listselltrade.detail'} onPress={() => {item.sell ? navigation.navigate("Deal Detail", {item:item}) : navigation.navigate("Trade Detail", {item:item})} } > <View style={{flex:1, flexDirection:"row", alignItems:"center", width:wp("100%"), height:wp("17%")}}> <View style={{width:wp("17%"), padding:wp("0.1%")}}> {sourceUri && sourceAltUri ? <CachedImage source={{uri:sourceUri}} sourceAlt={{uri:sourceAltUri}} style={styles.itemImage} /> : null} </View> <View style={{width:wp("25%")}}> <Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{item.artwork.item_name}</Text> </View> <View style={{width:wp("8%")}}> <Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{helper.returnStatus(item.status)}</Text> </View> <View style={{width:wp("7%")}}> <Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{item.price+item.shipping_cost}</Text> </View> <View style={{width:wp("3%"),flexDirection:"row"}}> {item.sell ? <Icon name='logo-usd' size={_size} /> : <Icon name='sync' size={_size} />} </View> <View style={{width:wp("8%")}}> <Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}> {item.artwork.category.category_name} </Text> </View> {item.sell && _me.id !== data.uploader_id && <View style={{width:wp("15%")}}> <TouchableOpacity onPress={() => {navigation.push('ListBuyExg', {uploader_id:item.artwork.uploader_id, title:_sellerposter_name})} }> <Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}> {_sellerposter_name} </Text> </TouchableOpacity> </View> } {!item.sell && _me.id !== data.uploader_id && <View style={{width:wp("15%")}}> <TouchableOpacity onPress={() => {navigation.push('ListBuyExg', {uploader_id:item.artwork.uploader_id, title:_sellerposter_name})} } > <Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}> {_sellerposter_name} </Text> </TouchableOpacity> </View> } <View style={{width:wp("20%")}}> <Text style={{fontSize:wp("3%"), flexWrap:"wrap"}}>{item.active_date.substring(0,10)}</Text> </View> </View> </TouchableOpacity> );
当前Jest测试代码
test ('has screen detail', async () => { const route = {params: {title:"my title", uploader_id:8}}; //8 is a test user. all is listed const navigation = {navigate:jest.fn()}; const propsVal = {device_id:"a device id"}; const authVal = {result:"fdkfjdsl;fjdsafkl", myself:{id:1,name:"me"}}; const stateVal = {name:"me", alias:"akkus", aka:"aka"}; const items = [{artwork:{item_name:"work name"}}]; //<<==mock state items const component = ( <NavigationContainer> <propsContext.Provider value={propsVal}> <authContext.Provider value={authVal}> <stateContext.Provider value={stateVal}> <ListSellTrade route={route} navigation={navigation}/> </stateContext.Provider> </authContext.Provider> </propsContext.Provider> </NavigationContainer>); const wrapper = render(component); expect(screen.getByText('my title')).toBeTruthy(); //<<==pass expect(screen.getByText('work name')).toBeTruthy(); //<<==failed expect(screen.getByTestId('listselltrade.detail')).toBeTruthy(); //<<==failed })
正确Mock内部状态的方法
你当前定义的items变量只是测试里的局部变量,不会影响组件内部的状态,以下是三种可行方案:
方案1:Mock useEffect的数据源(推荐)
如果items是在useEffect中通过API请求、自定义钩子或外部依赖获取的,直接Mock这个依赖返回预设数据:
- 若用axios请求数据:
jest.mock('axios', () => ({ get: jest.fn().mockResolvedValue({ data: [{ artwork: { item_name: "work name", category: { category_name: "test" } }, status: 'active', price: 100, shipping_cost: 10, sell: true, active_date: '2024-01-01' }] }) }));
- 若用自定义钩子
useFetchItems:
jest.mock('../hooks/useFetchItems', () => () => ({ items: [{ artwork: { item_name: "work name", category: { category_name: "test" } }, status: 'active', price: 100, shipping_cost: 10, sell: true, active_date: '2024-01-01' }], loading: false }));
方案2:直接Mock组件的内部状态(适合简单场景)
用jest.spyOn模拟useState,让其返回预设的items:
import React, { useState } from 'react'; // 在测试开始前Mock useState const mockUseState = jest.spyOn(React, 'useState'); mockUseState.mockImplementation(initial => { // 匹配组件中items的初始状态(假设初始为空数组) if (Array.isArray(initial) && initial.length === 0) { return [[{ artwork: { item_name: "work name", category: { category_name: "test" } }, status: 'active', price: 100, shipping_cost: 10, sell: true, active_date: '2024-01-01' }], jest.fn()]; } // 其他状态保持原有逻辑 return React.useState(initial); });
方案3:重构组件,将items作为props传入(最佳实践)
把组件改成受控组件,items通过props传入,测试时直接传mock数据即可,这是最易维护的测试方案:
组件修改
// 新增items props,去掉内部useState和useEffect赋值逻辑 const ListSellTrade = ({ route, navigation, items }) => { // ...其他原有代码 return <FlatList data={items} renderItem={/* 原有renderItem逻辑 */} />; };
测试修改
test ('has screen detail', async () => { const route = {params: {title:"my title", uploader_id:8}}; const navigation = {navigate:jest.fn(), push: jest.fn()}; const propsVal = {device_id:"a device id"}; const authVal = {result:"fdkfjdsl;fjdsafkl", myself:{id:1,name:"me"}}; const stateVal = {name:"me", alias:"akkus", aka:"aka"}; // 补充完整item所需的所有字段,避免渲染时因undefined报错 const mockItems = [{ artwork: { item_name: "work name", category: { category_name: "test" }, uploader_id: 8 }, status: 'active', price: 100, shipping_cost: 10, sell: true, active_date: '2024-01-01' }]; const component = ( <NavigationContainer> <propsContext.Provider value={propsVal}> <authContext.Provider value={authVal}> <stateContext.Provider value={stateVal}> <ListSellTrade route={route} navigation={navigation} items={mockItems} /> </stateContext.Provider> </authContext.Provider> </propsContext.Provider> </NavigationContainer>); render(component); expect(screen.getByText('my title')).toBeTruthy(); expect(screen.getByText('work name')).toBeTruthy(); expect(screen.getByTestId('listselltrade.detail')).toBeTruthy(); })
关键注意事项
- 补全mock item的字段:你的renderItem用到了
item.status、item.price、artwork.category等多个字段,只传item_name会导致渲染报错,必须补全所有依赖字段。 - 异步场景用waitFor:如果
items是通过异步请求获取的,要用waitFor包裹断言,确保异步操作完成后再检查:
await waitFor(() => { expect(screen.getByText('work name')).toBeTruthy(); });
内容的提问来源于stack exchange,提问作者user938363
相关产品推荐
相关产品推荐

