So I'm trying to test a form input. First the form should display an error and then after a value is entered the user clicks a button and the error should be removed.
I think I understand why this is happening but I can't figure out how to test this feature.
I'm conditionally rendering the error message and updating the state so I know that is probably why it's telling me to wrap my test in act()
. I'm also telling the mock server to send an error state and I figured maybe since I overrode the initial handler that it would only respond with the error state that I defined. That's not working either.
I'm not sure how exactly to test this since I'm new to testing. I really appreciate any help.
Here is the test function:
it('clears validation error after username field is updated', async () => {
let validationError;
server.use(generateValidationError('username', 'Username cannot be null'));
setup();
await act(async () => {
userEvent.click(button);
validationError = await screen.findByText('Username cannot be null');
userEvent.type(usernameInput, 'username001');
userEvent.click(button);
expect(validationError).not.toBeInTheDocument();
});
});
CodePudding user response:
Since setting state in component or doing something asynchronously can take "some time" you should treat your situation as a potential promise-like situation.
To be sure your test always waits for any change that is happening, you can use waitFor
method and add callback function with your code to check some state. This is from React Testing library
it('clears validation error after username field is updated', async () => {
let validationError;
server.use(generateValidationError('username', 'Username cannot be null'));
setup();
await act(async () => {
userEvent.click(button);
userEvent.type(usernameInput, 'username001');
userEvent.click(button);
await waitFor(async () => {
validationError = await screen.findByText('Username cannot be null');
expect(validationError).not.toBeInTheDocument();
});
});
});
TIP
When you are asserting something that includes DOM element, always select that element after you do some action (change validation state in your case after button click).
In the code above I change position of that selector regarding the above tip.
CodePudding user response:
In terms of approach, I would:
- Test the error is initially visible
- Click the button
- Test the error has disappeared
If you're going to use act()
, typically you place the assertions outside of its function body, see https://reactjs.org/docs/testing-recipes.html#act.
I'm not even sure you need act()
. The examples on the official testing-library docs seem to fulfil your needs:
- https://testing-library.com/docs/dom-testing-library/api-async#findby-queries
- https://testing-library.com/docs/react-testing-library/example-intro
CodePudding user response:
It was really silly mistake. The first error I was getting was because I was hiding my form after a successful submission. So the component would unmount and then I wouldn't be able to test it. I figured out a work around by testing each field separately and making sure it doesn't successfully submit the form.
By fixing the above mistake I didn't need to use act() anymore as I wasn't getting any errors in jest. Tests successfully passed afterwards.
it.each`
field | message | label
${'username'} | ${'Username cannot be null'} | ${'Username'}
${'email'} | ${'Email cannot be null'} | ${'Email'}
${'password'} | ${'Password cannot be null'} | ${'Password'}
`(
'clears validation error after $field field is updated',
async ({ field, message, label }) => {
server.use(generateValidationError(field, message));
setup();
userEvent.click(button);
const validationError = await screen.findByText(message);
const inputByLabel = screen.getByLabelText(label);
userEvent.type(
inputByLabel,
label === 'Email' ? '[email protected]' : 'username001'
);
//this line was needed to stop the form from submitting
userEvent.type(confirmPasswordInput, 'newpassword');
userEvent.click(button);
await waitFor(() => {
expect(validationError).not.toBeInTheDocument();
});
}
);