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

React Native单元测试遇TypeError:无法读取null的props属性

React Native单元测试报错:TypeError: Cannot read properties of null (reading 'props')

我使用Jest和@testing-library/react-native为React Native应用编写单元测试时,遇到了TypeError: Cannot read properties of null (reading 'props')错误。尝试用getByTestId替代queryByTestId无效,console.log(swiper)返回null,希望解决该问题。

组件代码

import { Button, StyleSheet, Text, View } from 'react-native'
import Swiper from "react-native-deck-swiper";
import React, { Component } from "react";
import { styles } from "./StyledSwipe.js";

function* range(start, end) {    
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

export default class Exemple extends Component {
  constructor(props) {
    super(props);
    this.state = {
      cards: [...range(1, 50)],  
      swipedAllCards: false,
      swipeDirection: "",
      cardIndex: 0,
    };
    this.swiper = {};
  }

  renderCard = (card, index) => {
    return (
      <View style={styles.card}>
        <Text style={styles.text}>
          {card}   
        </Text>
      </View>
    );
  };

  onSwiped = (type) => {
    console.log(`on swiped ${type}`);
  };

  onSwipedAllCards = () => {
    this.setState({
      swipedAllCards: true,
    });
  };

  swipeLeft = () => {
    this.swiper.swipeLeft();
  };

  render() {
    return (
      <View style={styles.container}>
        <Swiper
          testID="swipe"
          ref={(swiper) => {
            this.swiper = swiper;
          }}
          onSwiped={() => this.onSwiped("general")}
          onSwipedLeft={() => this.onSwiped("left")}
          onSwipedRight={() => this.onSwiped("right")}
          onSwipedTop={() => this.onSwiped("top")}
          onSwipedBottom={() => this.onSwiped("bottom")}
          onTapCard={this.swipeLeft}
          cards={this.state.cards}
          cardIndex={this.state.cardIndex}
          cardVerticalMargin={80}
          renderCard={this.renderCard}
          onSwipedAll={this.onSwipedAllCards}
          stackSize={3}
          stackSeparation={15}
          overlayLabels={{
            bottom: {
              title: "BLEAH",
              style: {
                label: {
                  backgroundColor: "black",
                  borderColor: "black",
                  color: "white",
                  borderWidth: 1,
                },
                wrapper: {
                  flexDirection: "column",
                  alignItems: "center",
                  justifyContent: "center",
                },
              },
            },
            left: {
              title: "NOPE",
              style: {
                label: {
                  backgroundColor: "black",
                  borderColor: "black",
                  color: "white",
                  borderWidth: 1,
                },
                wrapper: {
                  flexDirection: "column",
                  alignItems: "flex-end",
                  justifyContent: "flex-start",
                  marginTop: 30,
                  marginLeft: -30,
                },
              },
            },
            right: {
              title: "LIKE",
              style: {
                label: {
                  backgroundColor: "black",
                  borderColor: "black",
                  color: "white",
                  borderWidth: 1,
                },
                wrapper: {
                  flexDirection: "column",
                  alignItems: "flex-start",
                  justifyContent: "flex-start",
                  marginTop: 30,
                  marginLeft: 30,
                },
              },
            },
            top: {
              title: "SUPER LIKE",
              style: {
                label: {
                  backgroundColor: "black",
                  borderColor: "black",
                  color: "white",
                  borderWidth: 1,
                },
                wrapper: {
                  flexDirection: "column",
                  alignItems: "center",
                  justifyContent: "center",
                },
              },
            },
          }}
          animateOverlayLabelsOpacity
          animateCardOpacity
          swipeBackCard
        >
          <Button 
            testID="tillbaka"
            style={styles.backButton}
            onPress={() => this.swiper && this.swiper.swipeBack()}
            title="Tillbaka" />
        </Swiper>
      </View>
    );
  }
}

测试代码

import React from 'react';
import { Button, Text } from 'react-native';
import { shallow, configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { render, fireEvent, waitFor, getByText, getByTestId, queryByTestId} from '@testing-library/react-native';
import Exemple, {onTapCard, onSwiped, swipeBack, swipeLeft} from './Swipe.js';
import { act } from 'react-dom/test-utils';
import Swiper from 'react-native-deck-swiper';
  
describe('Exemple component', () => {
  test("logs swipe direction", () => {
    const spy = jest.spyOn(console, "log");
    const { queryByTestId } = render(<Exemple/>);
    const swiper = queryByTestId("swiper");
    console.log(swiper)

    fireEvent(swiper, "onSwipedLeft");

    expect(spy).toHaveBeenCalledWith("on swiped left");

    spy.mockClear();
    expect(swiper.props.swipedAllCards).toBe(true);
  });
});

报错信息

Exemple component › logs swipe direction

    TypeError: Cannot read properties of null (reading 'props')

      18 |     console.log(swiper)
      19 |
    > 20 |     fireEvent(swiper, "onSwipedLeft");
         |              ^
      21 |
      22 |     expect(spy).toHaveBeenCalledWith("on swiped left");
      23 |

      at props (node_modules/@testing-library/react-native/src/fireEvent.ts:95:22)
      at getEventHandler (node_modules/@testing-library/react-native/src/fireEvent.ts:79:19)
      at findEventHandler (node_modules/@testing-library/react-native/src/fireEvent.ts:112:19)
      at invokeEvent (node_modules/@testing-library/react-native/src/fireEvent.ts:143:12)
      at Object.<anonymous> (src/Components/Swipe/Swipe.test.js:20:14)

解决方案

1. 修正TestID匹配错误

组件中Swiper的testID是"swipe",但测试代码中使用queryByTestId("swiper")查找元素,直接导致找不到元素返回null。把测试代码中的查找语句改为:

const swiper = getByTestId("swipe");

推荐使用getByTestId替代queryByTestId,因为getByTestId会在找不到元素时直接抛出错误,更利于排查问题。

2. 修正fireEvent触发事件的方式

fireEvent(swiper, "onSwipedLeft")是错误的写法,@testing-library/react-native中触发组件事件时,不需要加on前缀,正确的写法是:

fireEvent(swiper, "swipeLeft");

3. 修正错误的断言逻辑

swipedAllCards是Exemple组件的state属性,并非Swiper组件的props,测试中expect(swiper.props.swipedAllCards).toBe(true)完全错误。可以通过组件实例获取state,或者模拟滑完所有卡片后验证状态。

修改后的测试代码示例

import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import Exemple from './Swipe.js';

describe('Exemple component', () => {
  test("logs swipe direction when swiped left", () => {
    // 模拟console.log避免污染测试输出
    const spy = jest.spyOn(console, "log").mockImplementation();
    const { getByTestId } = render(<Exemple/>);
    const swiper = getByTestId("swipe");

    // 正确触发左滑事件
    fireEvent(swiper, 'swipeLeft');

    expect(spy).toHaveBeenCalledWith("on swiped left");
    
    // 恢复console.log
    spy.mockRestore();
  });

  test("sets swipedAllCards to true after swiping all cards", async () => {
    const { getByTestId, instance } = render(<Exemple/>);
    const swiper = getByTestId("swipe");

    // 模拟滑完所有50张卡片
    for (let i = 0; i < 50; i++) {
      fireEvent(swiper, 'swipeLeft');
    }

    // 等待state更新完成
    await waitFor(() => {
      expect(instance.state.swipedAllCards).toBe(true);
    });
  });
});

额外优化点

  • 移除测试代码中未使用的enzyme相关导入,减少冗余
  • 不需要导入组件中未使用的方法(如onTapCard、swipeBack等)
  • 使用mockImplementation和mockRestore处理console.log,避免测试输出混乱

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 20:48:19