Closed codekirei closed 6 years ago
@codekirei Yeah, I'm having the same issue. Is this behaviour expected?
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.
https://github.com/airbnb/enzyme/blob/master/docs/future.md
"Event propagation is not supported". I assume that's what causes this problem.
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.
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>
);
@BLamy there are tons of ways to test it:
<form>
with an "onSubmit" prop that has a functionlogin
, 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.
@BLamy does wrapper.find('button').simulate('submit', { target: wrapper.find('button').get(0) })
work?
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
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();
});
});
@fernandopasik "wrapper.find(...).get(...).click is not a function" (This may be due to shallow instead of mount...)
@ZephD you can't call .get() with shallow rendering.
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)
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');
@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?
Hey guys, I was able to get around this by doing:
component.find('form')
.simulate('submit', { preventDefault () {} });
Looks hacky, but works.
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>
)
}
}
@vmasto Potentially it's because submit events bubble.
wrapper.find(...).get(...).click is not a function
Enzyme v3 with react 16 adapter also throws this.
@mrchief did you find a solution for this?
@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
Instead of simulating the submit
event on the form
you could simulate the event from the button itself.
wrapper.find('button').simulate('submit');
@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).
@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.
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.
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')
@onurarpapay it shouldn't work; the event name is "submit", not "onsubmit". Can you file a new issue about that one?
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.
Also, is this possible to do by simulating a keypress of enter on the submit button?
@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.
@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.
In that case, you'd want something like nightwatch/selenium or puppeteer/cypress, not a unit testing tool like enzyme.
@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?
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. ❇️
@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.
@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.
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();
});
});
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.
This is sad.
Instead of simulating the
submit
event on theform
you could simulate the event from the button itself.
wrapper.find('button').simulate('submit');
Thank you very much!!!
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");
});
});
Say I have a simple form to test:
If I
mount
this component withenzyme
, I can confirmonSubmit
is called by simulating asubmit
event on theform
element. However, if I simulate aclick
on the submit button, the form is not submitted andonSubmit
is not called.form.simulate('submit')
calls onSubmitbutton.simulate('click')
does not call onSubmitIs this working as intended? Seems inconsistent, but I could definitely be missing something.
Example repo here. In particular, compare the test cases.