React Native(Expo)项目运行Jest报意外token错误如何解决
在基于Expo的React Native项目中配置Jest单元测试环境时,执行测试命令抛出如下错误:Jest encountered an unexpected token ; SyntaxError: Invalid or unexpected token
错误截图如下:
已尝试Jest、Expo、React Native官方文档提及的所有配置方案,问题始终未解决,调整配置后还会抛出其他不同类型的错误。以下为项目相关配置与待测试文件代码。
package.json
{ "name": "contact", "version": "1.0.0", "main": "node_modules/expo/AppEntry.js", "scripts": { "start": "expo start", "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", "eject": "expo eject", "test": "jest" }, "jest": { "preset": "react-native", "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|)" ] }, "dependencies": { "@react-native-community/masked-view": "^0.1.11", "@reduxjs/toolkit": "^1.8.2", "expo": "~45.0.0", "expo-image-picker": "~13.1.1", "expo-media-library": "~14.1.0", "expo-status-bar": "~1.3.0", "react": "17.0.2", "react-dom": "17.0.2", "react-native": "0.68.2", "react-native-gesture-handler": "~2.2.1", "react-native-image-picker": "^4.8.4", "react-native-reanimated": "~2.8.0", "react-native-safe-area-context": "4.2.4", "react-native-screens": "~3.11.1", "react-native-web": "0.17.7", "react-navigation": "^4.4.4", "react-navigation-stack": "^2.10.4", "react-redux": "^8.0.2", "redux": "^4.2.0" }, "devDependencies": { "@babel/core": "^7.12.9", "@babel/preset-env": "^7.18.6", "@babel/preset-react": "^7.18.6", "@testing-library/react-native": "^10.1.1", "@types/jest": "^28.1.4", "babel-jest": "^28.1.2", "jest": "^28.1.2", "jest-environment-jsdom": "^28.1.2", "ts-jest": "^28.0.5", "typescript": "^4.7.4" }, "private": true }
babel.config.js
module.exports = function(api) { api.cache(true); return { presets: ['babel-preset-expo'], }; };
待测试页面文件 indexScreen.js
import React, {useState} from 'react'; import {View, Text, StyleSheet, FlatList, TouchableOpacity} from 'react-native'; import { Feather } from '@expo/vector-icons'; import {deleteContact} from '../redux/contactSlice'; import { useSelector,useDispatch } from 'react-redux'; import SearchBar from '../components/searchBar'; const indexScreen= function({navigation}) { const value = useSelector((state) => state.contacts); const dispatch = useDispatch(); const [term,setTerm] = useState(""); const [filteredValue, setFilteredValue] = useState([...value]); const searchFilter = (text) =>{ setTerm(text); if (term !==""){ const result = value.filter((item) => { return item.name.toLowerCase().includes(term.toLowerCase()) }); setFilteredValue(result); }else if (term === ""){ setFilteredValue([...value]); } } return( <View> <SearchBar term= {term} onTermChange = {newTerm => searchFilter(newTerm)} /> <Text>{term}</Text> <FlatList data= {term.length < 1 ? value:filteredValue} keyExtrator = {(contact) => contact.id} renderItem = {({item}) => { return( <TouchableOpacity onPress = {() => navigation.navigate('Show', {id: item.id})}> <View style ={styles.row}> <Text style = {styles.title}> {item.name}</Text> <TouchableOpacity onPress ={() => dispatch(deleteContact(item.id))}> <Feather style={styles.icon} name="trash" /> </TouchableOpacity> </View> </TouchableOpacity> ) }} /> </View> ) } indexScreen.navigationOptions = ({navigation}) => { return { headerRight: () => ( <TouchableOpacity onPress={() => navigation.navigate('Create')}> <Feather name = 'plus' size = {30}/> </TouchableOpacity> ), } }; const styles = StyleSheet.create({ row:{ flexDirection:'row', justifyContent:'space-between', paddingVertical:20, paddingHorizontal:10, borderTopWidth:1, borderColor:'grey' }, title:{ fontSize:18 }, icon: { fontSize:20 } }); export default indexScreen;
报错核心原因是Jest转译配置不符合Expo项目规则,存在3个配置错误:
- Jest preset使用错误:纯
react-nativepreset仅适用于React Native裸项目,Expo项目必须使用专属的jest-expopreset,否则无法正确转译Expo生态下的依赖包 transformIgnorePatterns正则语法错误:规则末尾多余的|导致正则匹配逻辑失效,需要转译的node_modules包未被处理,直接输出ES语法导致Jest解析失败- Babel测试环境转译规则缺失:缺少CommonJS转译插件,Jest运行在CommonJS环境下无法直接识别ES模块语法
按以下步骤修复:
安装缺失依赖
执行命令安装Expo专属Jest preset、对应React版本的测试渲染器、CommonJS转译插件:npm install --save-dev jest-expo react-test-renderer@17.0.2 @babel/plugin-transform-modules-commonjs
如果不需要测试Web端代码,直接卸载jest-environment-jsdom避免环境冲突:npm uninstall jest-environment-jsdom修正package.json中的Jest配置
替换原有jest字段内容,修复正则错误、更换正确preset、补充redux相关依赖的转译白名单:
"jest": { "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|react-redux|@reduxjs/toolkit)" ], "moduleFileExtensions": ["js", "jsx", "ts", "tsx", "json", "node"] }
- 修正babel.config.js配置
添加测试环境下的转译规则,修改后内容如下:
module.exports = function(api) { api.cache(true); return { presets: ['babel-preset-expo'], env: { test: { plugins: ['@babel/plugin-transform-modules-commonjs'] } } }; };
- 清除缓存重新运行测试
执行命令清除Jest历史缓存后重新运行测试即可解决语法报错:npx jest --clearCache && npm test
测试编写注意事项
由于待测试组件依赖Redux的useSelector、useDispatch hook,编写测试用例时需要将组件包裹在Redux Provider中,否则会抛出hook调用错误,示例测试代码如下:
import React from 'react'; import { render } from '@testing-library/react-native'; import { Provider } from 'react-redux'; import store from '../redux/store'; // 替换为项目实际的store文件路径 import IndexScreen from './indexScreen'; test('页面可正常渲染', () => { const mockNavigation = { navigate: jest.fn() }; render( <Provider store={store}> <IndexScreen navigation={mockNavigation} /> </Provider> ); // 补充对应断言逻辑 });
内容的提问来源于stack exchange,提问作者julian8991

