Jest测试通过却报环境销毁后访问错误,act使用存疑求助
解决Jest测试环境销毁后访问的错误及act使用确认
问题说明
Jest登录按钮测试用例已通过,但抛出以下错误:
ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down. From app/screens/login/tests/login.screen.test.tsx.
at Timeout._onTimeout (node_modules/react-native/jest/setup.js:417:26)
现有登录测试代码
import React from 'react'; import { render, fireEvent, act, waitFor } from '@testing-library/react-native'; import { LoginScreen } from '../login.screen'; describe('Login screen', () => { //Testing Login button it('should go to project screen on login', async () => { const navigationMock = { navigate: jest.fn() }; const { getByTestId } = render(<LoginScreen navigation={navigationMock} />); // Find the login inputs and enter test credentials const emailInput = getByTestId('email-input'); const passwordInput = getByTestId('password-input'); await act(async () => { fireEvent.changeText(emailInput, 'test@example.com'); fireEvent.changeText(passwordInput, 'password'); }); // Find the login button and trigger a press event const loginButton = getByTestId('login-button'); await act(async () => { fireEvent.press(loginButton); await waitFor(() => expect(navigationMock.navigate).toHaveBeenCalledWith('Project')); }); }); });
LoginScreen组件核心代码
import React from 'react'; import { SafeAreaView, View, TouchableOpacity, Text } from 'react-native'; import { Button, Card, TextInput } from 'react-native-paper'; import { loginStyle } from './login.style'; import { Image } from 'react-native'; import { Formik } from 'formik'; import * as yup from 'yup'; interface LoginScreenProps { navigation: any; } const validationSchema = yup.object().shape({ email: yup.string().email('That is an invalid email!').required('Please enter your email address!'), password: yup.string().required('Please enter your password!'), }); export const LoginScreen = (props: LoginScreenProps) => { const login = () => props.navigation.navigate('Project'); interface LoginFormValues { email: string; password: string; } const handleFormSubmit = (values: LoginFormValues) => { login(); }; return ( <SafeAreaView style={loginStyle.content}> <View style={loginStyle.view}> <Image source={require('../images/logo/logo.png')} style={loginStyle.logo} resizeMode="contain" /> <Card> <Card.Title title="ABC" subtitle="DEF" titleStyle={loginStyle.cardTitle} subtitleStyle={loginStyle.cardSubtitle} /> <Card.Content> <Formik initialValues={{ email: '', password: '' }} validationSchema={validationSchema} onSubmit={handleFormSubmit} > {({ handleChange, handleBlur, handleSubmit, values, errors }) => ( <> <Button onPress={() => handleSubmit()} style={loginStyle.cardButton} mode="contained" testID="login-button" > Login </Button> </> )} </Formik> </Card.Content> </Card> </View> </SafeAreaView> ); }; export default LoginScreen;
注册按钮测试参考代码
//...other code // Testing Register button it('should go to register screen when register', async () => { jest.useFakeTimers(); const navigationMock = { navigate: jest.fn() }; const { getByTestId } = render(<LoginScreen navigation={navigationMock} />, { wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, }); const registerButton = getByTestId('register-button'); await act(async () => { fireEvent.press(registerButton); }); act(() => { jest.runAllTimers(); }); expect(navigationMock.navigate).toHaveBeenCalledWith('Register'); jest.useRealTimers(); }); // other code...
解决方案及act使用确认
1. 修复测试异步逻辑与报错问题
报错原因是测试完成后仍有未清理的定时器在访问已销毁的Jest环境,结合你的注册测试方案,调整登录测试如下:
describe('Login screen', () => { it('should go to project screen on login', async () => { jest.useFakeTimers(); const navigationMock = { navigate: jest.fn() }; const { getByTestId } = render(<LoginScreen navigation={navigationMock} />); // 输入账号密码(无需手动包裹act) const emailInput = getByTestId('email-input'); const passwordInput = getByTestId('password-input'); fireEvent.changeText(emailInput, 'test@example.com'); fireEvent.changeText(passwordInput, 'password'); // 点击登录按钮 const loginButton = getByTestId('login-button'); fireEvent.press(loginButton); // 运行所有定时器并验证 act(() => { jest.runAllTimers(); }); expect(navigationMock.navigate).toHaveBeenCalledWith('Project'); // 恢复真实定时器 jest.useRealTimers(); }); });
2. 关于act的正确使用
- 无需手动包裹fireEvent/changeText:
@testing-library/react-native的fireEvent、render等API已经自动将操作包裹在act中,手动嵌套会造成冗余,甚至引发异步时序问题。 - 仅在处理自定义异步操作时使用act:比如你自己编写的setTimeout、Promise回调等,需要用
act包裹以确保React状态更新完成后再执行断言。
3. 额外注意事项
- 确保测试完成后清理所有fake timers:可以在
afterEach中添加清理逻辑,避免测试间的定时器污染。
afterEach(() => { jest.useRealTimers(); jest.clearAllTimers(); });
内容的提问来源于stack exchange,提问作者Yanhamu
相关产品推荐
相关产品推荐

