Search code examples
reactjsreact-hooksjestjsreact-testing-library

React testing library failing but I'm sure the code is correct


I'm trying to learn react-testing-library but I have a failure here that I'm sure should work and can't find the error.

It's a simple name and email app where you add a name and email and it is added to a list.

The home has a UserFrom and UserList "use client"

import UserForm from './components/UserForm'
import { useState } from 'react'
import UserList from './components/UserList'

export interface User{
  name: string,
  email: string
}

export default function Home() {

  const [users, setUsers] = useState<User[]>([])

  const onUserAdd = (user:User) => {
    setUsers([...users, user])
  }

  return (
    <>
      <h1>RTL App</h1>
      <UserForm onUserAdd={onUserAdd}/>
      <hr />
      <UserList users={users}/>
    </>
  )
}

User Form take the name and email and passed it back to home

"use client"
import React, { useState } from 'react'


const UserForm = ({ onUserAdd }: any) => {

    const [email, setEmail] = useState('')
    const [name, setName] = useState('')

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault()
        onUserAdd({ name, email })
    }

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label htmlFor="name">Name</label>
                <input id="name" type="text" value={name} onChange={(e) => setName(e.target.value)} />
            </div>
            <div>
                <label htmlFor="email">Email</label>
                <input id="email" type="text" value={email} onChange={(e) => setEmail(e.target.value)} />
            </div>
            <button>Add User</button>
        </form>
    )
}

export default UserForm

UserList then displays the name and email

"use client"
import React from 'react'

const UserList = ({users}:any) => {

    const renderUsers = users.map((user:any) => {
        return(
            <tr key={user.name}>
                <td>{user.name}</td>
                <td>{user.email}</td>
            </tr>
        )
    })

  return (
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Email</th>
            </tr>
        </thead>
        <tbody>
            {renderUsers}
        </tbody>
    </table>
  )
}

export default UserList

The test are simply to test there are two inputs and a button and test the Useradd function and that the name and email are passed

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import UserForm from './UserForm'

test('it show two inputs and a button', () => {
    render(<UserForm />);
    const inputs = screen.getAllByRole('textbox');
    const button = screen.getByRole('button');

    expect(inputs).toHaveLength(2);
    expect(button).toBeInTheDocument();
});

test('it calls onUserAdd when form submitted', () => {
    const mock = jest.fn();

    render(<UserForm onUserAdd={mock} />);
    const nameInput = screen.getByRole('textbox', { name: /name/i });
    const emailInput = screen.getByRole('textbox', { name: /email/i });
    userEvent.type(nameInput, 'jane');
    userEvent.type(emailInput, 'jane@jane.com');
    const button = screen.getByRole('button');
    userEvent.click(button);

    expect(mock).toHaveBeenCalled();
    expect(mock).toHaveBeenCalledWith({ name: 'jane', email: 'jane@jane.com' });
});

The error I get says the mock function isn't called but I can't work out why.

Can anyone see what I'm missing

✓ it show two inputs and a button (84 ms) ✕ it calls onUserAdd when form submitted (41 ms)

● it calls onUserAdd when form submitted

expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

  23 |     userEvent.click(button);
  24 |
> 25 |     expect(mock).toHaveBeenCalled();
     |                  ^
  26 |     expect(mock).toHaveBeenCalledWith({ name: 'jane', email: 'jane@jane.com' });
  27 | });
  28 |

Solution

  • If you are using @testing-library/user-event version >= 14, please use await before any API. There are some examples on official doc

    import React from 'react';
    import { render, screen } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import '@testing-library/jest-dom';
    import UserForm from './UserForm';
    
    test('it show two inputs and a button', () => {
        render(<UserForm />);
        const inputs = screen.getAllByRole('textbox');
        const button = screen.getByRole('button');
    
        expect(inputs).toHaveLength(2);
        expect(button).toBeInTheDocument();
    });
    
    test('it calls onUserAdd when form submitted', async () => {
        const mock = jest.fn();
    
        render(<UserForm onUserAdd={mock} />);
        const nameInput = screen.getByRole('textbox', { name: /name/i });
        const emailInput = screen.getByRole('textbox', { name: /email/i });
        await userEvent.type(nameInput, 'jane');
        await userEvent.type(emailInput, 'jane@jane.com');
        const button = screen.getByRole('button');
        await userEvent.click(button);
    
        expect(mock).toHaveBeenCalled();
        expect(mock).toHaveBeenCalledWith({ name: 'jane', email: 'jane@jane.com' });
    });
    

    Test result:

     PASS  stackoverflow/77369394/UserForm.test.tsx
      ✓ it show two inputs and a button (54 ms)
      ✓ it calls onUserAdd when form submitted (112 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        1.266 s, estimated 7 s
    Ran all test suites related to changed files.
    

    package version:

    "@testing-library/user-event": "^14.4.3",
    

    Related question: userEvent in React Testing Library Doesn't Cause onClick To Be Called