Expo项目Jest测试App组件遇TypeError: Cannot read properties of null (reading 'match')
Expo + Jest测试App组件时出现TypeError: Cannot read properties of null (reading 'match')问题
错误详情
TypeError: Cannot read properties of null (reading 'match')
1 | import 'react-native-gesture-handler'; > 2 | import { registerRootComponent } from 'expo'; | ^ 3 | import React, { useState, useEffect,useCallback } from "react"; 4 | import { useFonts } from 'expo-font'; 5 | import * as SplashScreen from 'expo-splash-screen'; at getDevServer (node_modules/react-native/Libraries/Core/Devtools/getDevServer.js:31:29) at Object.<anonymous> (node_modules/expo/src/environment/logging.fx.ts:15:39) at Object.<anonymous> (node_modules/expo/src/Expo.fx.tsx:3:1) at Object.<anonymous> (node_modules/expo/src/Expo.ts:1:1) at Object.<anonymous> (src/App.tsx:2:1) at Object.<anonymous> (src/__tests__/navigation/App.test.tsx:9:1)
项目相关配置与代码
package.json
"dependencies": { "jest": "^29.2.1", "jest-expo": "^49.0.0", "react-test-renderer": "^18.2.0", "ts-node": "^10.9.1", }, "devDependencies": { "@babel/core": "^7.20.0", "@jest/types": "^29.6.3", "@testing-library/jest-dom": "^6.1.4", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "^12.3.0", "@types/jest": "^29.5.6", "@types/react": "~18.2.14", "@types/react-native": "^0.72.2", "@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/parser": "^6.8.0", "eslint": "^8.2.0", "eslint-config-prettier": "^9.0.0", "eslint-config-universe": "^12.0.0", "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "prettier": "^3.0.3", "ts-jest": "^29.1.1", "typescript": "^5.1.3" },
App.tsx
import 'react-native-gesture-handler'; import { registerRootComponent } from 'expo'; import React, { useState, useEffect,useCallback } from "react"; import { useFonts } from 'expo-font'; import * as SplashScreen from 'expo-splash-screen'; import { StyleSheet, Text, View,ScrollView,SafeAreaView,PixelRatio } from 'react-native'; import RootDrawerNavigator from './navigation/rootDrawerNavigator/RootDrawerNavigator' SplashScreen.preventAutoHideAsync(); const App = () => { const designResolution = { width: 1528, height: 750 } console.log(PixelRatio.getFontScale()) const [fontsLoaded, fontError] = useFonts({ 'DMRegular': require('./assets/fonts/DMSans-Regular.ttf'), 'DMMedium': require('./assets/fonts/DMSans-Medium.ttf'), 'DMBold': require('./assets/fonts/DMSans-Bold.ttf'), 'DMLight': require('./assets/fonts/DMSans-ExtraLight.ttf'), }); const onLayoutRootView = useCallback(async () => { if (fontsLoaded || fontError) { await SplashScreen.hideAsync(); } }, [fontsLoaded, fontError]); if (!fontsLoaded && !fontError) { return null; } return ( <RootDrawerNavigator /> ); } registerRootComponent(App); const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, }); export default App
App.test.tsx
import * as React from 'react'; import { screen, render, fireEvent ,waitFor} from '@testing-library/react-native'; import {expect, jest, test} from '@jest/globals'; import '@testing-library/jest-native/'; import { NavigationContainer } from '@react-navigation/native'; import renderer from "react-test-renderer" import { ClientStoreStack, RootDrawerNavigator } from '../../navigation'; import { DashboardScreen } from '../../screens'; import App from '../../App'; jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); describe('render correctly', () => { it('renders App', () => { const tree = renderer .create(<App />).toJSON(); expect(tree).toMatchSnapshot(); }); });
jest.config.ts
import type {Config} from 'jest'; const config: Config = { verbose: true, "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)" ], "collectCoverage": true, "collectCoverageFrom": [ "**/*.{ts,tsx}", "!**/coverage/**", "!**/node_modules/**", "!**/babel.config.js", "!**/jest.setup.ts", "!**/pettierrc.js", "!**/eslintrc.js" ], "setupFilesAfterEnv": ["@testing-library/jest-native/extend-expect", "<rootDir>/src/jest.setup.ts"], "setupFiles": [ ], }; export default config;
setup.jest.ts(React Navigation配置)
import '@testing-library/jest-native/extend-expect'; import 'react-native-gesture-handler/jestSetup'; import {expect, jest, test} from '@jest/globals'; jest.mock('react-native-reanimated', () => { const Reanimated = require('react-native-reanimated/mock'); Reanimated.default.call = () => {}; return Reanimated; }); jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
解决方法
1. 补充全局window.location mock
在setup.jest.ts中添加以下代码,模拟浏览器环境的location对象,解决getDevServer函数的依赖问题:
// Mock window.location to fix getDevServer error global.window = { ...global.window, location: { href: 'http://localhost:8081', protocol: 'http:', host: 'localhost:8081', hostname: 'localhost', port: '8081', pathname: '/', search: '', hash: '', origin: 'http://localhost:8081', }, };
2. Mock Expo相关模块
在setup.jest.ts中添加对Expo模块的mock,避免测试时加载不必要的环境初始化代码:
// Mock expo modules jest.mock('expo', () => ({ registerRootComponent: jest.fn(), })); // Mock expo-splash-screen jest.mock('expo-splash-screen', () => ({ preventAutoHideAsync: jest.fn(), hideAsync: jest.fn(), })); // Mock expo-font jest.mock('expo-font', () => ({ useFonts: jest.fn(() => [true, null]), // 模拟字体已加载完成 }));
3. 调整测试文件(可选)
如果测试只需要验证组件渲染,可以直接测试RootDrawerNavigator而非整个App,跳过Expo的根组件注册逻辑:
// 修改App.test.tsx import * as React from 'react'; import renderer from "react-test-renderer" import { RootDrawerNavigator } from '../../navigation'; import { NavigationContainer } from '@react-navigation/native'; jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); describe('render correctly', () => { it('renders RootDrawerNavigator', () => { const tree = renderer .create( <NavigationContainer> <RootDrawerNavigator /> </NavigationContainer> ).toJSON(); expect(tree).toMatchSnapshot(); }); });
4. 验证配置
确保jest.config.ts中的setupFilesAfterEnv正确引入了setup.jest.ts,并且所有mock逻辑在测试执行前加载。
内容的提问来源于stack exchange,提问作者mermaid
相关产品推荐
相关产品推荐

