如何让Jest测试识别JSX/TSX语法?遇Unexpected token '<'报错
运行Jest测试时,测试代码中的JSX标签无法被解析,报错"Jest encountered an unexpected token",具体错误如下:
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
- If you are trying to use ECMAScript Modules, see Jest相关文档
- If you are trying to use TypeScript, see Jest相关文档
- To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
- If you need a custom transformation specify a "transform" option in your config.
- If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
Jest配置文档
For information about custom transformations, see:
Jest代码转换文档
Details:/Users/myname/projects/rn/tictactoe/__tests__/QuestionBoard.test.tsx:27 const { getByText } = (0, react_native_1.render)>(<QuestionBoard_1.default questions={questions} onSubmit={jest.fn()}/>); ^SyntaxError: Unexpected token '<'
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)
项目配置文件
eslintrc.js
module.exports = { root: true, extends: '@react-native', overrides: [ { // Test files only files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], extends: ['plugin:@testing-library/react-native',], }, ], };
babel.config.js
module.exports = { presets: ['module:metro-react-native-babel-preset'], plugins: ['@babel/plugin-transform-runtime'], };
jest.config.js
module.exports = { preset: 'react-native', setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], transform: { '^.+\\.jsx?$': 'babel-jest', '^.+\\.tsx?$': 'ts-jest', }, transformIgnorePatterns: [ 'node_modules/(?!(react-native|@react-native|@testing-library/react-native|@aws-amplify|uuid)/)', ], moduleNameMapper: { '\\.(css|less)$': 'identity-obj-proxy', testEnvironment: 'node', }, };
jest.setup.js
import '@testing-library/jest-native/extend-expect';
tsconfig.json
{ "extends": "@react-native/typescript-config/tsconfig.json", "compilerOptions": { "types": ["jest", "@testing-library/jest-native"], "skipLibCheck": true } }
package.json
{ "name": "tictactoe", "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", "test": "jest" }, "dependencies": { "@aws-amplify/api": "^6.1.8", "@aws-amplify/auth": "^6.8.3", "@aws-amplify/cli": "^12.13.1", "@aws-amplify/react-native": "^1.1.6", "@react-native-async-storage/async-storage": "^2.1.0", "@react-native-community/netinfo": "^11.4.1", "@react-navigation/native": "^7.0.14", "@react-navigation/stack": "^7.1.0", "aws-amplify": "^6.10.3", "expo": "^52.0.18", "global": "^4.4.0", "react": "18.3.1", "react-native": "^0.76.5", "react-native-gesture-handler": "^2.21.2", "react-native-get-random-values": "^1.11.0", "react-native-reanimated": "^3.16.5", "react-native-safe-area-context": "^5.0.0", "react-native-screens": "^4.3.0", "react-native-vector-icons": "^10.2.0", "uuid": "^11.0.3" }, "devDependencies": { "@babel/core": "^7.26.0", "@babel/plugin-transform-runtime": "^7.25.9", "@babel/preset-env": "^7.26.0", "@babel/preset-react": "^7.26.3", "@babel/runtime": "^7.25.0", "@react-native-community/cli": "15.0.1", "@react-native-community/cli-platform-android": "^15.1.3", "@react-native-community/cli-platform-ios": "^15.1.3", "@react-native/babel-preset": "0.76.5", "@react-native/eslint-config": "0.76.5", "@react-native/metro-config": "0.76.5", "@react-native/typescript-config": "0.76.5", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "^12.9.0", "@types/jest": "^29.5.14", "@types/react": "^19.0.2", "@types/react-test-renderer": "^19.0.0", "@types/uuid": "^10.0.0", "babel-jest": "^29.7.0", "eslint": "^8.19.0", "jest": "^29.7.0", "metro-react-native-babel-preset": "^0.77.0", "prettier": "2.8.8", "react-test-renderer": "^19.0.0", "ts-jest": "^29.2.5", "typescript": "5.0.4" }, "engines": { "node": ">=18" }, "packageManager": "yarn@1.22.22" }
组件代码
import { Pressable, ScrollView, Text, TextInput, View } from 'react-native'; import * as React from 'react'; type QuestionsBoardProps = { questions: string[]; onSubmit: (obj: {}) => void; }; function QuestionsBoard({ questions, onSubmit }: QuestionsBoardProps) { const [data, setData] = React.useState({}); return ( <ScrollView> {questions.map((q, index) => ( <View key={q}> <Text>{q}</Text> <TextInput accessibilityLabel="answer input" accessibilityHint="input" onChangeText={(text) => setData((state) => ({ ...state, [index + 1]: { q, a: text }, })) } /> </View> ))} <Pressable accessibilityRole="button" onPress={() => onSubmit(data)}> <Text>Submit</Text> </Pressable> </ScrollView> ); } export default QuestionsBoard;
测试代码
import React from 'react'; import { render } from '@testing-library/react-native'; import QuestionBoard from '../QuestionBoard'; test('renders QuestionsBoard component', () => { const questions = ['q1', 'q2']; const { getByText } = render( <QuestionBoard questions={questions} onSubmit={jest.fn()} /> ); expect(getByText('q1')).toBeTruthy(); expect(getByText('q2')).toBeTruthy(); });
解决方法
1. 修复jest.config.js的语法错误与配置问题
你的jest.config.js存在两处关键问题:testEnvironment被错误嵌套在moduleNameMapper内部,且node环境不支持JSX解析。修改后的配置如下:
module.exports = { preset: 'react-native', setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], transform: { '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', // 统一用babel-jest处理所有JS/TS文件,metro预设已支持TSX }, transformIgnorePatterns: [ 'node_modules/(?!(react-native|@react-native|@testing-library/react-native|@aws-amplify|uuid|@react-navigation)/)', // 添加导航库到转换白名单 ], moduleNameMapper: { '\\.(css|less)$': 'identity-obj-proxy', }, testEnvironment: 'jsdom', // 切换到支持JSX的jsdom环境 };
2. 保留babel.config.js现有配置
module:metro-react-native-babel-preset已经内置了JSX和TypeScript的转换支持,无需额外添加其他预设,保持原配置即可。
3. 验证tsconfig.json的JSX设置
继承自@react-native/typescript-config/tsconfig.json的配置已默认开启"jsx": "react-native",无需修改。
4. 清除缓存并重新运行测试
执行以下命令清除Jest缓存,避免旧配置残留:
jest --clearCache
然后重新运行测试:
yarn test
以上修改会确保Jest正确转换JSX/TSX语法,解决Unexpected token '<'的错误。
内容的提问来源于stack exchange,提问作者johnrubythecat

