I do some unit testing, here is my code:
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, '[email protected]');
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'));
});
});
});
The JEST test passes, but I get the following error:
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)
Honestly, I'm new to coding, so I've used some AI tools like ChatGPT to get some help, but that didn't work.
Here is the login.screen.tsx code:
import React from 'react';
import { SafeAreaView, View, TouchableOpacity, Text } from 'react-native';
import { Button, Card, TextInput } from 'react-native-paper';
// other imports
import { loginStyle } from './login.style';
import { Image } from 'react-native';
import { Formik } from 'formik';
import * as yup from 'yup';
// other imports
// define interface
interface LoginScreenProps {
navigation: any;
// other code
}
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) => {
//console.log(props);
// other code
const login = () => props.navigation.navigate('Project');
// other code
// Define the type for your form values
interface LoginFormValues {
email: string;
password: string;
}
// Handle form values
const handleFormSubmit = (values: LoginFormValues) => {
//console.log(values); // Handle form submission logic here
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 }) => (
<>
{/* other code */}
<Button
onPress={() => handleSubmit()}
style={loginStyle.cardButton}
mode="contained"
testID="login-button"
>
Login
</Button>
</>
)}
</Formik>
{/* other code */}
</Card.Content>
</Card>
{/* other code */}
</View>
</SafeAreaView>
);
};
// other code
export default LoginScreen;
And here is the example code for the solution for testing my Register button:
//...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...
I've seen that error a couple of times, it generally has to do with applying your asynchronous test code improperly. It is definetly an issue in your testCode and not in your component. I think the issue is that you use a nested await waitFor() inside you await act(). It should all work without having to apply jest.useFakeTimers(), and I would avoid using that, because it might lead to problems down the road. Also, don't put multiple fireEvents in 1 async act().
I think this should work: