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

如何在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();
})

关键注意事项
  1. 补全mock item的字段:你的renderItem用到了item.status、item.price、artwork.category等多个字段,只传item_name会导致渲染报错,必须补全所有依赖字段。
  2. 异步场景用waitFor:如果items是通过异步请求获取的,要用waitFor包裹断言,确保异步操作完成后再检查:
await waitFor(() => {
  expect(screen.getByText('work name')).toBeTruthy();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 10:36:07