Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing React form onSubmit with jest

I have a very simple form component which expects an onSubmit handler.

const NoteForm = ({ handleNoteOnSubmit }) => (
  <form onSubmit={handleNoteOnSubmit}>
    <input name="note" id="note" />
    <button type="submit">save</button>
  </form>
);

export default NoteForm;

The onSubmit handler looks like this.

  const handleNoteSubmit = (event) => {
    event.preventDefault();
    const noteObject = {
      content: event.target.note.value
    };

    // save note here....
  };

In my test, I want to assert that when I submit my form, the handler receives the data I submit.

describe('NoteForm', () => {
  test('<NoteForm /> calls onSubmit', async () => {
    const handleSubmit = jest.fn();
    const { container } = render(
      <NoteForm handleNoteOnSubmit={handleSubmit} />
    );

    const noteInput = container.querySelector('input[name="note"]');
    fireEvent.change(noteInput, {
      target: { name: 'note', value: 'testing a form...' },
    });

    fireEvent.submit(container.querySelector('form'));
    expect(handleSubmit).toHaveBeenCalledWith(
      expect.objectContaining({
        target: { note: { value: 'testing a form...' } },
      })
    );
  });
});

Instead, my handler receives a SyntheticBaseEvent, which contains the following:

    + SyntheticBaseEvent {
    +   "_reactName": "onSubmit",
    +   "_targetInst": null,
    +   "bubbles": true,
    +   "cancelable": true,
    +   "currentTarget": null,
    +   "defaultPrevented": false,
    +   "eventPhase": 3,
    +   "isDefaultPrevented": [Function functionThatReturnsFalse],
    +   "isPropagationStopped": [Function functionThatReturnsFalse],
    +   "isTrusted": true,
    +   "nativeEvent": Event {
    +     "isTrusted": true,
    +   },
    +   "target": <form>
    +     <input
    +       id="note"
    +       name="note"
    +     />
    +     <button
    +       type="submit"
    +     >
    +       save
    +     </button>
    +   </form>,
    +   "timeStamp": 1679044636947,
    +   "type": "submit",
      },

I have looked at similar questions and everywhere it seems to do take a similar approach, here and there with some different forms of expect. But in every case, I always have this SyntheticBaseEvent which doesn't seem to contain my form data.

like image 471
jameskentdev Avatar asked Feb 19 '26 13:02

jameskentdev


1 Answers

You should use elements to access the form control - the input element in your case.

e.target is the form element, so if you want to get the value of the input element, you should access the e.target.elements.note.value property.

We should call e.presist() in our mock handleSubmit event handler to avoid the below warning:

Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the method isDefaultPrevented on a released/nullified synthetic event. This is a no-op function. If you must keep the original synthetic event around, use event.persist().

At last, the e, e.target, e.target.elements, and e.target.elements.note objects, each of it do not just contain one property, so we should use expect.objectContaining() to match subset of these objects.

E.g.

note-form.jsx:

import React from "react";

const NoteForm = ({ handleNoteOnSubmit }) => (
  <form onSubmit={handleNoteOnSubmit}>
    <input name="note" id="note" />
    <button type="submit">save</button>
  </form>
);

export default NoteForm;

note-form.test.jsx:

import { render, fireEvent } from '@testing-library/react';
import React from 'react';
import NoteForm from './note-form';

describe('NoteForm', () => {
  test('<NoteForm /> calls onSubmit', async () => {
    const handleSubmit = jest.fn().mockImplementation((e) => {
      e.persist();
    });
    const { container } = render(<NoteForm handleNoteOnSubmit={handleSubmit} />);

    const noteInput = container.querySelector('input[name="note"]');
    fireEvent.change(noteInput, {
      target: { name: 'note', value: 'testing a form...' },
    });

    fireEvent.submit(container.querySelector('form'));
    expect(handleSubmit).toHaveBeenCalledWith(
      expect.objectContaining({
        target: expect.objectContaining({
          elements: expect.objectContaining({ note: expect.objectContaining({ value: 'testing a form...' }) }),
        }),
      })
    );
  });
});

Test result:

 PASS  stackoverflow/75766171/note-form.test.jsx (7.2 s)
  NoteForm
    ✓ <NoteForm /> calls onSubmit (24 ms)

---------------|---------|----------|---------|---------|-------------------
File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------|---------|----------|---------|---------|-------------------
All files      |     100 |      100 |     100 |     100 |                   
 note-form.jsx |     100 |      100 |     100 |     100 |                   
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        7.493 s

package versions:

"@testing-library/react": "^11.2.7",
"jest": "^26.6.3",
"react": "^16.14.0",
like image 111
slideshowp2 Avatar answered Feb 22 '26 06:02

slideshowp2



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!