You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 22:49:53