Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 309
daniel Avatar asked Nov 06 '19 22:11

daniel


2 Answers

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.

like image 97
laserany Avatar answered Oct 20 '22 18:10

laserany


I have previously answered a similar question here: https://stackoverflow.com/a/61869203/8879071

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!

like image 28
dhananzen Avatar answered Oct 20 '22 16:10

dhananzen