How to test a function has been called when submitted within a form using react-testing-library?
Asked Answered
L

3

10

In my React Signup component is a form where the user inputs their email, password, and password confirmation. I am trying to write tests using jest/react-testing-library, however I keep getting a test failed as the received number of function calls is 0 with an expected number of calls being 1.

I have tried variations of the Jest matcher such as .toHaveBeenCalled(), .toHaveBeenCalledWith(arg1, arg2, ...), toBeCalled() all of which still expect a value of 1 or greater but fail because the received number is 0. I have tried both fireEvent.click and fireEvent.submit both of which still fail.

Signup.js

export const Signup = ({ history }) => {
  const classes = useStyles();

  const [signup, setSignup] = useState({
    email: null,
    password: null,
    passwordConfirmation: null,
  });
  const [signupError, setSignupError] = useState('');

  const handleInputChange = e => {
    const { name, value } = e.target;

    setSignup({ ...signup, [name]: value });
    console.log(signup);
  };

  const submitSignup = e => {
    e.preventDefault();
    console.log(
      `Email: ${signup.email}, Pass: ${signup.password}, Conf: ${signup.passwordConfirmation}, Err: ${signupError}`
    );
};

return (
    <main>
        <form onSubmit={e => submitSignup(e)} className={classes.form}>
         <TextField onChange={handleInputChange}/>
         <TextField onChange={handleInputChange}/>
         <TextField onChange={handleInputChange}/>
         <Button
            type="submit">
           Submit
         </Button>

Signup.test.js

import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { render, cleanup, fireEvent } from '@testing-library/react';

import { Signup } from '../Components/Signup';

afterEach(cleanup);

const exampleSignup = {
  email: '[email protected]',
  password: 'test123',
  passwordConfirm: 'test123',
};

describe('<Signup />', () => {
  test('account creation form', () => {

    const onSubmit = jest.fn();

    const { getByLabelText, getByText } = render(
      <BrowserRouter>
        <Signup onSubmit={onSubmit} />
      </BrowserRouter>
    );

    const emailInput = getByLabelText(/Enter your Email */i);
    fireEvent.change(emailInput, { target: { value: exampleSignup.email } });
    const passInput = getByLabelText(/Create a Password */i);
    fireEvent.change(passInput, { target: { value: exampleSignup.password } });
    const passCInput = getByLabelText(/Confirm Password */i);
    fireEvent.change(passCInput, {
      target: { value: exampleSignup.passwordConfirm },
    });

    fireEvent.submit(getByText(/Submit/i));

    expect(onSubmit).toHaveBeenCalledTimes(1);
  });
});

Results from test run account creation form

expect(jest.fn()).toHaveBeenCalledTimes(expected)

Expected number of calls: 1
Received number of calls: 0
Ledbetter answered 6/11, 2019 at 22:35 Comment(0)
T
2

in your SignUp.test.js you are passing your onSubmit function as a prop however that prop is never used in your SignUp component. That is why your onSubmit function is never called.

Now regarding the answer to your question, the react testing library discourages testing implementation details (like testing a function has been called) therefor there is no way to test that using the react testing library (although you can do that with other frameworks like using jest.spyOn on your function so that would be a way to do it)

What is recommended though is testing the outcome of your submit function. For example let's say you want to display (Thank you for signing up) after clicking the submit button, in that case you would test that using expect(screen.getByText("Thank you for signing up")).toBeInTheDocument() after running fireEvent.submit(getByText(/Submit/i))

FYI : Since you are using jest, you don't need to call the cleanup function afterEach test.

Thwart answered 20/3, 2021 at 8:57 Comment(0)
N
2

I have previously answered a similar question here: https://mcmap.net/q/1168102/-how-to-mock-a-function-and-expect-it-to-be-called

Your submit action is not doing any side effect really here (except for logging it). You can assert console.log called with something. NOT A GOOD IDEA!

Usually, on form submissions, we do some side-effect as follows:


  1. FUNCTION CALLS: You would be calling something (a prop function, API library util) that is not a part of the component (aka dependency.)

Prop: pass a jest.fn() as a prop and can be asserted.

API util: axios/fetch can be mocked and asserted.

Only the dependencies can be mocked.


  1. VISUAL SIDE-EFFECTS:

example, errors/success messages etc. Assert on the elements being toBeInTheDocument()


  1. FORM CONTENTS

Form contents can be asserted, if you're very interested. e.g toHaveFormValues()


Summarising, internal functions (which have no reference outside) can't be mocked. AND SHOULD NOT BE!

Nonsuit answered 10/12, 2021 at 16:13 Comment(0)
S
0

The submit event has to be called at form tag. You can add a data-testid attribute at form tag to get it on test.

fireEvent.submit(getByTestid('form'));

Simony answered 14/6, 2020 at 22:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.