Jest+@testing-library/react-native测试自定义Input组件报ReferenceError错误
问题描述
开发了一款支持传入必选及可选props的React Native自定义Input组件,使用Jest与@testing-library/react-native进行单元测试,执行npm run test命令时出现报错,其余组件测试用例均可正常运行,仅Input组件测试失败,尝试移除部分props仍无法定位故障原因。
报错信息
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. Check the render method of `Input`. at Input (/Users/jefflewis/Documents/Computer-Programming/Projects-Libraries/unsion/unison-ui-react-native/src/components/inputs/Input.tsx:24:20)
测试文件代码(Input.test.tsx)
// Imports: Dependencies import React from 'react'; import renderer from 'react-test-renderer'; import { cleanup } from '@testing-library/react-native'; // Imports: Components import Input from '../Input'; // React Native Testing Library: Cleanup (Unmounts Component And Destroys Container) afterEach(cleanup); // Tests: Input describe('Input', () => { // Renders Component it('renders component', () => { renderer.create(<Input placeholder="Placeholder" value="Value" onChangeText={() => console.log('Hi')} />); }); });
组件文件代码(Input.tsx)
// Imports: Dependencies import React, { useEffect, useState, useRef } from 'react'; import { Dimensions, StyleSheet, Text, TextInput, View } from 'react-native'; // Imports: Styles import { defaultStyles } from '../../styles/styles'; // Imports: TypeScript Types import { IInputProps } from '../../types/inputTypes'; // Screen Dimensions const { width } = Dimensions.get('window'); // Component: Input const Input: React.FC<IInputProps> = ({ value, onChangeText, placeholder, placeholderTextColor, label, darkMode, autoCapitalize, autoCompleteType, // Android Only autoCorrect, autoFocus, blurOnSubmit, caretHidden, clearButtonMode, // iOS Only clearTextOnFocus, // iOS Only dataDetectorTypes, // iOS Only defaultValue, editable, enablesReturnKeyAutomatically, // iOS Only keyboardType, maxLength, multiline, numberOfLines, // iOS Only onSubmitEditing, returnKeyType, secureTextEntry, spellCheck, // iOS Only textAlign, textContentType, // iOS Only // passwordRules, // iOS Only }): JSX.Element => { // React Hooks: State const [text, setText] = useState<string>(''); // React Hooks: Refs const textInputRef: React.RefObject<TextInput> = useRef(null); // React Hooks: Lifecycle Methods useEffect(() => { // Set State setText(value); }, [value]); // Render Label const renderLabel = (): JSX.Element | undefined => { // Check If Prop Exists if (label) { return <Text style={darkMode ? styles.labelTextDark : styles.labelTextLight}>{label}</Text>; } }; return ( <View style={styles.container}> <>{renderLabel()}</> <TextInput ref={textInputRef} style={darkMode ? styles.inputTextDark : styles.inputTextLight} placeholder={placeholder} placeholderTextColor={placeholderTextColor ? defaultStyles.colorDarkLabelTertiary : defaultStyles.colorLightLabelTertiary} value={text} onChangeText={onChangeText} autoCapitalize={autoCapitalize || 'none'} autoCompleteType={autoCompleteType || 'off'} // Android Only autoCorrect={autoCorrect} autoFocus={autoFocus} blurOnSubmit={blurOnSubmit} caretHidden={caretHidden} clearButtonMode={clearButtonMode || 'never'} // iOS Only clearTextOnFocus={clearTextOnFocus} // iOS Only dataDetectorTypes={dataDetectorTypes || 'none'} // iOS Only defaultValue={defaultValue} editable={editable} enablesReturnKeyAutomatically={enablesReturnKeyAutomatically} // iOS Only keyboardAppearance={darkMode ? 'dark' : 'light'} // iOS Only keyboardType={keyboardType || 'default'} maxLength={maxLength || 1000} multiline={multiline} numberOfLines={numberOfLines || 1} // Android Only onSubmitEditing={onSubmitEditing} returnKeyType={returnKeyType || 'done'} secureTextEntry={secureTextEntry} spellCheck={spellCheck} // iOS Only textAlign={textAlign || 'left'} textContentType={textContentType || 'none'} // iOS Only /> </View> ); }; // Styles const styles = StyleSheet.create({ container: { height: 'auto', width: width - defaultStyles.marginExtraLarge, marginBottom: defaultStyles.marginMedium, }, labelTextLight: { color: defaultStyles.colorDarkLabelSecondary, fontSize: defaultStyles.fontSizeFootnoteSmall, fontWeight: defaultStyles.fontWeightSemiBold, letterSpacing: defaultStyles.fontLetterSpacingFootnote, textTransform: 'uppercase', }, labelTextDark: { color: defaultStyles.colorLightLabelSecondary, fontSize: defaultStyles.fontSizeFootnoteSmall, fontWeight: defaultStyles.fontWeightSemiBold, letterSpacing: defaultStyles.fontLetterSpacingFootnote, textTransform: 'uppercase', }, inputTextLight: { height: 40, fontSize: defaultStyles.fontSizeSubheading, color: defaultStyles.colorLightLabel, borderColor: defaultStyles.colorLightBorder, borderBottomWidth: StyleSheet.hairlineWidth, // textOverflow: 'ellipsis', }, inputTextDark: { height: 40, fontSize: defaultStyles.fontSizeSubheading, color: defaultStyles.colorDarkLabel, borderColor: defaultStyles.colorDarkBorder, borderBottomWidth: StyleSheet.hairlineWidth, // textOverflow: 'ellipsis', }, }); // Exports export default Input;
解决方案
以下是问题的根因和对应修复步骤:
- 测试库混用导致环境冲突
你同时使用了react-test-renderer和@testing-library/react-native的cleanup逻辑,两个库的生命周期处理逻辑冲突会导致Jest环境提前销毁,触发import after torn down报错。建议统一使用@testing-library/react-native的渲染逻辑,替换测试文件内的渲染代码即可:
// 移除react-test-renderer导入 import { render, screen } from '@testing-library/react-native'; // 修改测试用例 it('renders component', () => { render(<Input placeholder="Placeholder" value="Value" onChangeText={() => console.log('Hi')} />); expect(screen.getByPlaceholderText('Placeholder')).toBeTruthy(); });
如果要继续使用react-test-renderer,直接删除@testing-library/react-native的cleanup导入和afterEach(cleanup)逻辑即可。
- 样式导入不规范
报错里提到的type is invalid大概率来自你导入的defaultStyles对象,检查../../styles/styles文件的导出方式:如果是默认导出就改成import defaultStyles from '../../styles/styles',如果是命名导出确认导出的变量名没有拼写错误。测试环境下如果样式文件有副作用,也可以在Jest配置里对样式文件做mock:
// jest.config.js module.exports = { moduleNameMapper: { '^../../styles/styles$': '<rootDir>/__mocks__/stylesMock.ts', } }; // __mocks__/stylesMock.ts export const defaultStyles = { marginExtraLarge: 32, marginMedium: 16, colorDarkLabelSecondary: '#000', // 补全所有用到的样式字段即可 };
- 空返回值兼容问题
renderLabel方法在没有传入label时返回undefined,空标签<>{undefined}</>在部分Jest模拟环境下会被识别为无效类型,修改render部分的逻辑即可:
// 替换<>{renderLabel()}</>为 {label && renderLabel()}
内容的提问来源于stack exchange,提问作者jefelewis
相关产品推荐
相关产品推荐

