Search code examples
react-testing-libraryreact-hook-formzod

Testing fail with react-hook-form and zod


I'm trying to test a simple form built with react-hook-form and zod.

My form has 2 fields: email and password. It's basiclly the same as the example on the docs, except that I use zod to validate form field instead of passing options into register function. My test is quite simple as well, just click the button and expect to receive 2 alert on the DOM. But somehow it fail.

Here is the form component:

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'

const formSchema = z.object({
  email: z.string().email(),
  password: z.string(),
})

type Input = z.infer<typeof formSchema>

export function SimpleForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Input>({ resolver: zodResolver(formSchema) })

  function onSubmit() {}

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor='email'>email</label>
      <input id='email' {...register('email')} type='email' />
      {errors.email && <span role='alert'>{errors.email.message}</span>}
      <label htmlFor='password'>password</label>
      <input id='password' {...register('password')} type='password' />
      {errors.password && <span role='alert'>{errors.password.message}</span>}
      <button type='submit'>SUBMIT</button>
    </form>
  )
}

Here is the test file:

import { SimpleForm } from './simple-form'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'

describe('SimpleForm', () => {
  it('should display required error when value is invalid', async () => {
    render(<SimpleForm />)

    const user = userEvent.setup()

    await user.click(screen.getByRole('button'))

    expect(await screen.findAllByRole('alert')).toHaveLength(2)
  })
})

and the error:

SimpleForm › should display required error when value is invalid      

    Unable to find role="alert"

    Ignored nodes: comments, script, style
    <body>
      <div>
        <form>
          <label
            for="email"
          >
            email
          </label>
          <input
            id="email"
            name="email"
            type="email"
          />
          <label
            for="password"
          >
            password
          </label>
          <input
            id="password"
            name="password"
            type="password"
          />
          <button
            type="submit"
          >
            SUBMIT
          </button>
        </form>
      </div>
    </body>

      12 |     await user.click(screen.getByRole('button'))
      13 |
    > 14 |     expect(await screen.findAllByRole('alert')).toHaveLength(2)
         |                         ^
      15 |   })
      16 | })
      17 |

Solution

  • I fix this by using waitFor.

    import { render, screen, waitFor } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import { SimpleForm } from './test-form';
    
    describe('SimpleForm', () => {
      it('should display required error when value is invalid', async () => {
        render(<SimpleForm />);
    
        const user = userEvent.setup();
    
        await user.click(screen.getByRole('button'));
    
        waitFor(() => {
          const alerts = screen.getAllByRole('alert');
          return expect(alerts.length).toBe(2);
        });
      });
    });