enzyme: simulated click on submit button in form does not submit form

Say I have a simple form to test:

<form onSubmit={onSubmit} >
  <input />
  <button type="submit">Submit</button>
</form>

If I mount this component with enzyme, I can confirm onSubmit is called by simulating a submit event on the form element. However, if I simulate a click on the submit button, the form is not submitted and onSubmit is not called.

  • form.simulate('submit') calls onSubmit
  • button.simulate('click') does not call onSubmit

Is this working as intended? Seems inconsistent, but I could definitely be missing something.

Example repo here. In particular, compare the test cases.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 150
  • Comments: 41 (9 by maintainers)

Most upvoted comments

The form submits if you use the submit event on the button.

const onSubmit = sinon.spy();
const wrapper = mount(
    <form onSubmit={onSubmit} />
);
const button = wrapper.find('button');
button.simulate('submit');

@BLamy there are tons of ways to test it:

  • shallow-render it, and assert on the presence of the two inputs and the button
  • shallow-render it, and assert that the root is a <form> with an “onSubmit” prop that has a function
  • pass in a spy for login, and then unit-test the function on the prop and assert that it calls your spy with the right arguments.

What further testing is needed? In no way should you be trying to write tests that test React’s “onSubmit” functionality, or of browsers’ submit behavior - that’s the job of the React team, or the browser implementors, respectively.

@ljharb I was talking about no way of testing it using simulate.

If you .simulate('click') it doesn’t work. If you .simulate('submit') then e is not bound to the right context and therefore doesn’t have a .target property.

So yeah I can stub e and directly call onSubmit with a spy on login but realistically what I wanted to test was to make sure I was referencing my input fields right.

For me the solution was to get the DOM element and trigger the click without using simulate.

import { mount, shallow } from 'enzyme';
import Button from '../button/button.js';
import Field from './field.js';
import Form from './form.js';
import React from 'react';
import { spy } from 'sinon';

describe('Form', () => {
  it('submit event when click submit', () => {
    const callback = spy();
    const wrapper = mount(
    <Form onSubmit={ callback }>
        <Field id="firstName" name="firstName" />
        <Field id="lastName" name="lastName" />
        <Field id="email" name="email" type="email" />
        <footer>
          <Button caption="Send" display="primary" type="submit" />
          <Button caption="Clear" type="reset" />
        </footer>
      </Form>
    );
    wrapper.find('[type="submit"]').get(0).click();
    expect(callback).to.have.been.called();
  });
});

Hey guys, I was able to get around this by doing:

component.find('form')
      .simulate('submit', { preventDefault () {} });

Looks hacky, but works.

wrapper.find(...).get(...).click is not a function

Enzyme v3 with react 16 adapter also throws this.

Instead of simulating the submit event on the form you could simulate the event from the button itself.

wrapper.find('button').simulate('submit');

Especially concerned, that this is mount and should, by design, simulate real DOM behaviour. Or maybe DOM simulation through mount has some limitation – and in this case it should be explicitly specified.

@fernandopasik “wrapper.find(…).get(…).click is not a function” (This may be due to shallow instead of mount…)

@BLamy does wrapper.find('button').simulate('submit', { target: wrapper.find('button').get(0) }) work?

https://github.com/airbnb/enzyme/blob/master/docs/future.md

“Event propagation is not supported”. I assume that’s what causes this problem.

@Schachte I think I used these instead:

wrapper.find('form').simulate('submit')

// or

wrapper.find('.btn-text').simulate('click')  // btn-text is simply an example. you can use your own selector

Best I could do:

    const form = component.find('form').at(0),
    const children = form.render().children().children();
    form.simulate('submit', { target: { children } });

Which works but leaves this in the console

Warning: ReactComponentTreeDevtool: Missing React element for debugID 5 when building stack
Warning: ReactComponentTreeDevtool: Missing React element for debugID 5 when building stack
Warning: ReactComponentTreeDevtool: Missing React element for debugID 5 when building stack
Warning: ReactComponentTreeDevtool: Missing React element for debugID 5 when building stack

I’m using tape for my test

This is pretty rough. So if I’m getting this straight there is currently no way to test code which looks like this?

const LoginComponent = ({login}) => (
    <form
      onSubmit={e => {
        const username = e.target.children[0].value;
        const password = e.target.children[1].value;
        login(username, password);
      }}
    >
      <input type="text" placeholder="Username" />
      <input type="text" placeholder="Password" />
      <button type="submit">Sign In</button>
    </form>
);

Better would be expect(callback).to.have.property('callCount', 1) or something similar; noop getter assertions are very dangerous (because expect(callback).to.have.been.yogurt will silently pass, and is always a bug)

@netrocc’s solution seems to work, however I’m not sure why buttons are able to receive submit events. Is this something we can rely upon?

@ZephD you can’t call .get() with shallow rendering.

I agree with @colinramsay. If any of you guys want to put up a PR to integrate Event Propagation, i’m sure the maintainers would greatly appreciate it.

Can somebody post a full working example please.

I have tried all the suggested solutions above using mount with a simple type submit button inside a form with onSubmit and it does not work still.

@mrchief it’s totally fine for your tests for a given component, to rely on that component’s instance (and it having one). In general, I’d suggest never using simulate, and directly invoking prop functions. One solution is to directly test that invoking those props does the right thing; or you can mock out instance methods, test that the prop functions call them, and unit test the instance methods. Either is fine, and it’ll depend on what your code is doing.

@ajc24 I like how your post started but then, you end up testing the browser, not your unit. We all know that a submit button inside a form will fire the submit event and it’s the browser’s job to test that part. You should rather be testing that the submit handler does what it’s supposed to and if you can ensure that, then you can leave testing the browser out of your unit tests and still be ok. Am I missing something?

@cppbit I don’t think this is a solution, because this enzyme approach doesn’t honor the type of the rendered <button> within mount().

In my situation, I have a React component that is a wrapper for an HTML <button>. And I want the <button> type to be dynamic, as chosen by the wrapper component. I was hoping to test the correct rendering and behavior, by seeing of the <form> onSubmit callback is called (or not called).

I used @sohailykhan94’s solution to test if a form submittal was properly executing a handler function. Here’s what it looks like, and it seems to work well with my Jest/Enzyme testing environment.

it('executes a handler function on submittal', () => {
    const form = cmp().find('form')

    expect(cmp().state().submitted).toEqual(false)
    form.simulate('submit', { preventDefault () {} })
    expect(cmp().state().submitted).toEqual(true)
})

Here is my example Form React Component. Of course this component is going to have more features, but I wanted to test if this worked, before building out the rest of it.

import React, { Component } from 'react'

export default class SimpleForm extends Component {
  constructor(props) {
    super(props)

    this.state = {
      firstName: '',
      email: '',
      submitted: false
    }
  }

  submitForm = () => {
    this.setState({ submitted: true })
  }

  render() {
    return (
      <form className="simple-form" onSubmit={this.submitForm}>

        <input name="firstName" />
        <input name="email" />

        <button type="submit">Submit</button>
      </form>
    )
  }
}

@MartinDawson no, it’s not - and your test should be testing that the onSubmit prop does what you expect. Your test should not be verifying that the browser, or react itself, works properly - that’s a job for those projects’ tests.

@vmasto Potentially it’s because submit events bubble.