Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simulated click on submit button in form does not submit form #308

Closed
codekirei opened this issue Apr 9, 2016 · 41 comments
Closed

simulated click on submit button in form does not submit form #308

codekirei opened this issue Apr 9, 2016 · 41 comments

Comments

@codekirei
Copy link

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.

@leopoldjoy
Copy link

@codekirei Yeah, I'm having the same issue. Is this behaviour expected?

@prokaktus
Copy link

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.

@colinramsay
Copy link

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

"Event propagation is not supported". I assume that's what causes this problem.

@blainekasten
Copy link
Contributor

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.

@BLamy
Copy link

BLamy commented Jul 12, 2016

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>
);

@ljharb
Copy link
Member

ljharb commented Jul 12, 2016

@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.

@BLamy
Copy link

BLamy commented Jul 13, 2016

@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.

@ljharb
Copy link
Member

ljharb commented Jul 13, 2016

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

@BLamy
Copy link

BLamy commented Jul 13, 2016

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

@fernandopasik
Copy link

fernandopasik commented Oct 24, 2016

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();
  });
});

@ZephD
Copy link

ZephD commented Jan 18, 2017

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

@fernandopasik
Copy link

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

@ljharb
Copy link
Member

ljharb commented Feb 14, 2017

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
Copy link

netrocc commented Apr 4, 2017

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');

@vmasto
Copy link

vmasto commented Sep 6, 2017

@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?

@sohailykhan94
Copy link

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

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

Looks hacky, but works.

@DZuz14
Copy link

DZuz14 commented Oct 15, 2017

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>
    )
  }
}

@jamesseanwright
Copy link

@vmasto Potentially it's because submit events bubble.

@mrchief
Copy link

mrchief commented Nov 9, 2017

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

Enzyme v3 with react 16 adapter also throws this.

@Schachte
Copy link

@mrchief did you find a solution for this?

@mrchief
Copy link

mrchief commented Jan 13, 2018

@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

@tresor616
Copy link

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

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

@majew7
Copy link

majew7 commented Jun 22, 2018

@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).

@tresor616
Copy link

@majew7 from what you are describing it doesn’t sound like your scenario is the same as the one in the topic of this thread which I was contributing towards. In this scenario it’s a form with an onSubmit and the corresponding static submit button type which is predictable.

Perhaps share your code snippet and we can help you out with your specific scenario where the button has a dynamic type within your wrapper component. The event simulation is a signal sent to a component, if the component is not expecting the event then it won’t work.

@ljharb
Copy link
Member

ljharb commented Jul 4, 2018

This is sadly working as intended. simulate does not faithfully simulate anything - it's just sugar for invoking a prop function with a mapped event name.

I'm going to close this with a recommendation to avoid simulate entirely.

@onurarpapay
Copy link

hello, well I was struggling with simulate('submit') for one full day. It was working, i.e. it was raising the submit event but I always got a "TypeError: handler.apply is not a function."
Issue is solved with only changing 'submit' to 'onsubmit'. I don't understand why. 'onSubmit' also works.
So it is:

form.simulate('onsubmit')

@ljharb
Copy link
Member

ljharb commented Aug 15, 2018

@onurarpapay it shouldn't work; the event name is "submit", not "onsubmit". Can you file a new issue about that one?

@MartinDawson
Copy link

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.

@MartinDawson
Copy link

Also, is this possible to do by simulating a keypress of enter on the submit button?

@ljharb
Copy link
Member

ljharb commented Dec 13, 2018

@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.

@MartinDawson
Copy link

@ljharb I understand that but I was trying to do behavior driven development so I thought it would have been better to try and do it as close as possible to browser.

@ljharb
Copy link
Member

ljharb commented Dec 14, 2018

In that case, you'd want something like nightwatch/selenium or puppeteer/cypress, not a unit testing tool like enzyme.

@mrchief
Copy link

mrchief commented Jan 15, 2019

@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?

@Peripona
Copy link

Peripona commented Mar 4, 2019

Working Solution - React Enzyme jest component Test

sharing an online working good short example.
Well the problem is not just form submit it could be any input type where the attached event handler is using the event to get the target value or even a preventDefault. So i am going to attach a full code example of how we can test it.
ReactComponentTestWithJestAndEnzyme

In this example i have created a Checkbox and attached an event Handler,
when one triggers the event onChange if we are expecting anything other than this which in this case is event. the test calling the simulate need to mock this. or just to say pass the target object with value.
Just go to the Tests tab and see the test running there. found in the CheckboxWithLabel.test.js file.

Hope this helps someone. ❇️

@mrchief
Copy link

mrchief commented Mar 4, 2019

@Peripona Thanks for posting the sandbox, it's going to make it easy for people to try out different solutions. (I also love your handle "Peri-Pona" :))

I've since moved away from relying on simulate. I don't need to test the browser's part of firing the event, nor the React's part of calling my event handler when that happens. Instead, what I actually want to test is when my event handler is called, it does its job correctly. So I use wrapper.instance().onChange(...) to test the handlers - Updated CodeSandbox.

This is not a silver bullet and whether it's the right approach or not depends on one's use case; e.g. one might argue that reaching out to wrapper.instance().onChange is relying too much on internal mechanisms and that would be a valid argument. Another argument would be that wrapper.instance will not work for functional components (in those cases you can simply export the handler and test it out separately).

I'm not sure if there is an absolute right or wrong in this case or even one way is more right than the other - just that we have options for those who get to this issue searching for answers. I hope @ljharb can chime on whether relying on wrapper.instance is a good idea or not.

@ljharb
Copy link
Member

ljharb commented Mar 5, 2019

@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.

@mliq
Copy link

mliq commented May 24, 2019

My solve is this:
As long as <button> has null or undefined for onClick method, a click will propagate to the parent.

So, I am testing for that directly:

describe('<Button> with no handleClick prop', () => {
    const wrapper = shallow(<Button>{MOCK_CONTENT}</Button>);

    it('renders with null or undefined onClick method (allowing click to bubble up to form onSubmit)', () => {
        expect(wrapper.prop('onClick')).toBeFalsy();
    });
});

@vjekofalco
Copy link

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');

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');

If you have disabled property on button you will submit the form no mater if it is disabled or not.

@cristian-eriomenco

This comment has been minimized.

@JSEvgeny
Copy link

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

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

Thank you very much!!!

@MeroVinggen
Copy link

MeroVinggen commented Sep 6, 2022

Found solution for me:

Switched onFinish form callback to onClick callback on submit button:

onClick={() => form.validateFields().then(<onFinishCallback>, ()=>{})}

In test:

it('', async() => {
  ...
  // without await not working, you can switch it to await new Promise((res) => setTimeout(res, 50)); after act also wotk
  await act(async () => {
    <submitButton>.simulate("click");
  });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests