I try to create an integrated test for a sign in component but I am facing some issues. Basically I need to check that after entered email and password credentials and clicked the submit button, it redirect me to a given page. I am using waitForComponentToBeRemoved to check the none presence of email or password field of the sign in component but I got an error:
Timed out in waitForElementToBeRemoved.
Please tell me if i dont have the right approach or if you need further informations.
Here is the test:
it( 'Login with real username and password', async () => {
beforeEach(() => {
fetch.dontMock()
})
act(() => {
ReactDOM.render(<App />, container);
})
// log /
console.log('history', window.location.pathname)
const email = process.env.TEST_EMAIL
const password = process.env.TEST_PASSWORD
const signInButton = screen.getByTestId('landing-signin')
fireEvent.click(signInButton)
await waitFor(async () => {
expect(signInButton).not.toBeInTheDocument()
// log /signin
console.log('history', window.location.pathname)
})
const emailField = screen.getByPlaceholderText('Email address')
const passwordField = screen.getByPlaceholderText('Your password')
const button = screen.getByTestId('submit-button')
expect(button).toBeInTheDocument()
expect(passwordField).toBeInTheDocument()
expect(emailField).toBeInTheDocument()
userEvent.type(emailField, email)
userEvent.type(passwordField, password)
await act(async () => {
fireEvent.click(button)
})
// Timed out in waitForElementToBeRemoved.
await waitForElementToBeRemoved(button).then(() => {
console.log('element has been removed')
})
// i need something to wait the response and the redirection to be done
})
ps: I dont want to mock data, it need to do real api call
How to you do the redirect in your source code? Are you using react-router? (In this case you could simply mock the redirect function and check if it has been called!?)
Please check out this related question: Testing redirect after submit with React Testing Library
Kent C Dodds is building and testing a simple sign-in component in this video (starting at timestamp 14:30):
https://www.youtube.com/watch?v=eg_TFYF_cKM&t=869s&ab_channel=Applitools%3AVisualAIPoweredTestAutomation
Related
I've been struggling with this problem for a while. I have an Auth component inside which I try to access to local storage to see if there is a token in there and send it to server to validate that token.
if token is valid the user gets logged-in automatically.
./components/Auth.tsx
const Auth: React.FC<Props> = ({ children }) => {
const dispatch = useDispatch(); // I'm using redux-toolkit to mange the app-wide state
useEffect(() => {
if (typeof window !== "undefined") {
const token = localStorage.getItem("token");
const userId = localStorage.getItem("userId");
if (userId) {
axios
.post("/api/get-user-data", { userId, token })
.then((res) => {
dispatch(userActions.login(res.data.user)); // the user gets logged-in
})
.catch((error) => {
localStorage.clear();
console.log(error);
});
}
}
}, [dispatch]);
return <Fragment>{children}</Fragment>;
};
export default Auth;
then I wrap every page components with Auth.tsx in _app.tsx file in order to manage the authentication state globally.
./pages/_app.tsx
<Provider store={store}>
<Auth>
<Component {...pageProps} />
</Auth>
</Provider>
I have a user-profile page in which user can see all his/her information.
in this page first of all I check if the user is authenticated to access this page or not.
if not I redirect him to login page
./pages/user-profile.tsx
useEffect(() => {
if (isAuthenticated) {
// some code
} else {
router.push("/sign-in");
}
}, [isAuthenticated]);
The problem is when the user is in user-profile page and reloads . then the user always gets redirected to login-page even if the user is authenticated.
It's because the code in user-profile useEffect gets executed before the code in Auth component.
(user-profile page is a child to Auth component)
How should i run the code in Auth component before the code in user-profile page ?
I wanna get the user redirected only when he's not authenticated and run all the authentication-related codes before any other code.
Are you sure that the problem is that user-profile's useEffect is executed before Auth's useEffect? I would assume that the outermost useEffect is fired first.
What most probably happens in your case is that the code that you run in the Auth useEffect is asynchronous. You send a request to your API with Axios, then the useEffect method continues to run without waiting for the result. Normally, this is a good situation, but in your profile, you assume that you already have the result of this call.
You would probably have to implement an async function and await the result of both the axios.post method and dispatch method. You would need something like this:
useEffect(() => {
async () => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem("token")
const userId = localStorage.getItem("userId")
if (userId) {
try {
const resp = await axios.post("/api/get-user-data", {userId, token})
await dispatch(userActions.login(res.data.user)) // the user gets logged-in
} catch(error) {
localStorage.clear()
console.log(error)
}
}
}
}()
}, [dispatch])
I think this should work, but it would cause your components to wait for the response before anything is rendered.
I have about 5 separate context api states that I want to rest after logging out from app, since they casing lots of issues any idea how to rest them redirecting doesn't seem to be doing much, and I don't want to refresh, this issue is my log out function will logout the user from server and simply redirect to main page but the state is still in the memory ?
const SignOut = () => {
signOut(auth)
.then(() => {
// Sign-out successful.
})
.catch((error) => {
// An error happened.
const errorCode = error.code;
const errorMessage = error.message;
snackbar.Msg(error, 'A signout error has happened.');
});
history.push('/');
};
I am testing my App component by doing the authentication process (i start at landing page then click on button sign in to be redirect to the signin page , fill the email and password field and click to the signin button to access to the first page which is installations page in my case) but after a login successful response from the server, I am redirected to the sign in page and i can't figure out why. Here is the test in question:
it( 'Login', async () => {
render(<App />)
// Check we are on the good location at the begining of the test
await waitFor(() => expect(window.location.pathname).toBe('/'))
// env variable needed to connect to the account
const email = process.env.TEST_EMAIL
const password = process.env.TEST_PASSWORD
// // signIn button from the landing page
const signInButton = screen.getByTestId('landing-signin')
expect(signInButton).toBeInTheDocument()
// click action of this button
fireEvent.click(signInButton)
// check the path
await waitFor( async () => expect(window.location.pathname).toBe('/signin'))
// check the presence of elements in signin page
const emailField = screen.getByPlaceholderText('Email address')
const passwordField = screen.getByPlaceholderText('Your password')
expect(passwordField).toBeInTheDocument()
expect(emailField).toBeInTheDocument()
// fill the credentials
userEvent.type(emailField, email)
userEvent.type(passwordField, password)
const button = screen.getByTestId('submit-button')
expect(button).toBeInTheDocument()
// fire event login by clicking the button
act(() => {
userEvent.click(button)
})
// // check the path
await waitFor( async () => expect(window.location.pathname).toBe('/installations'))
// check the title of installation page to be in the document
const title = screen.getByText('Installation selection')
expect(title).toBeInTheDocument()
// check info to be in the document
const infoText = screen.getByText('Please, wait a little bit while we are fetching {{data}}')
expect(infoText).toBeInTheDocument()
// back to /signin
console.log('window.location.pathname', window.location.pathname)
})
I have jsdom enable, I am using Jest and React-testing-library. I find some elements of installation page but I get redirect to the sign in page.
I have given a task to code about sending email verification to user after they have registered themselves in signup screen which is react native based. Create new user is handle in one js file called AuthProvider.js. In one of the return value of AuthContext.Provider, there is one action which handle create new user which is shown code below and is working fine.
registerWithEmail: async (userDetails) => {
const { email, password } = userDetails;
return await auth()
.createUserWithEmailAndPassword(email, password)
.then(() => {
//wish to add on the send email verification action here
return true;
});
}
The return true code above is used to do checking in signup screen. If I wish to return the true value only if the condition where verification email is send to them and is clicked. How can I do it and can I have any kind of guidance?
You can use async-await syntax with `try-catch this way.
registerWithEmail: async (userDetails) => {
try {
const { email, password } = userDetails;
const {user} = await auth().createUserWithEmailAndPassword(email, password)
await user.sendEmailVerification()
return true
} catch (e) {
console.log(e)
return false
}
It'll return true only when the email is sent. If there's any error in the function it'll trigger catch and the function will return false.
I don't know more about react-native but in android studio when we send verification email and when user clicked on it. We have firebase function to check user clicked on verification link or not.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user.isEmailVerified())
{
// user is verified, so you can finish this screen or send user to other screen which you want.
}
i hope it will help you and give you some idea...
I'm testing the rejection of the submit event of my login form. If the user just submitted the form without filling up the username and password, the two error messages must show up and it should pass the test. But the result of the test shows the opposite: it shows that the username and password error messages are null. I tried using setTimeout() since the onSubmit event is asynchronous because of axios, but it still didn't pass the test. Is there anything wrong about the way I use the waitFor() utility for an asynchronous submit event?
it('Should render username and password error messages when both inputs are blank', () => {
const { getByTestId, queryByTestId } = render(<Index />)
fireEvent.submit(getByTestId('form'))
expect(getByTestId('submit-button').disabled).toBeTruthy()
setTimeout(async () => {
expect(getByTestId('submit-button').disabled).toBeFalsy()
const usernameError = await waitFor(() => queryByTestId('username-error'))
expect(usernameError).toBeInTheDocument()
const passwordError = await waitFor(() => queryByTestId('password-error'))
expect(passwordError).toBeInTheDocument()
}, 0)
})
There are a couple of changes to the test that might fix this problem. Please find them in the following code as comments
// The first is adding async to the callback of your `it` so you don't have to use a timeout
it('Should render username and password error messages when both inputs are blank', async () => {
const { getByTestId, findByTestId, queryByTestId } = render(<Index />)
expect(getByTestId('submit-button').disabled).toBeTruthy()
fireEvent.submit(getByTestId('form'))
// Here we can use waitFor which waits until the promise triggered by the last fireEvent finishes.
await waitFor(() => {
expect(getByTestId('submit-button').disabled).toBeFalsy()
})
// Finally, you should be able to just use getByTestId to locate the elements you need, and given that the promises are resolved, they should be in the document
expect(getByTestId('username-error')).toBeInTheDocument()
expect(getByTestId('password-error')).toBeInTheDocument()
})
Please if these recommendations don't work, also copy the code for the component being tested