Hello! Big fan of enzyme here.
I use Enzyme a lot for unit testing and I've noticed recently that people are consistently recommending people use instance prop functions versus using the simulate behavior provided by Enzyme because of the bad implementation.
Is there anyway we could get the docs updated with what the preferred solution is? It's not really clear as the docs stand right now.
Can we also add a note that there are plans to deprecate it Enzyme v4 too? I saw that in the comments and I was surprised by it. I'm more than happy to put in a PR if that is the preferred solution.
Cheers.
simulate is indeed deprecated; if there's ever a v4 of enzyme it will be removed with prejudice.
The reason it's bad is because it's named "simulate" and it takes an event name, but it does not actually faithfully simulate anything. It's based on the historic implementation of the same method in the shallow renderer which suffers from the same problems. All it does is transform the event name to a prop name, and invoke the function in that prop.
The only way a replacement would be worth considering is if it actually simulated the event - ie, simulating "change" produced a stream of events, including keypress/keydown/keyup/etc, input, change, blur, whatever made sense - and invoked all the expected handlers as a result. This is a very large project, however, and I don't think the knowledge even exists in a single package in the entire ecosystem yet to encapsulate it (it's only in browsers, and not something that could be referenced in this direction).
A PR to make this clear in the docs would be welcomed.
Awesome, I'll get started on a PR today.
@ljharb not sure if you saw but I opened a PR on Monday.
I saw; I’ll take a look as soon as i can - thanks!
All it does is transform the event name to a prop name, and invoke the function in that prop.
@ljharb with deprecation of .simulate(), are you planning to introduce some kind of a convenience method to dispatch events on a DOM element (like what react-testing-library does with their fireEvent function)?
If not, what would be an idiomatic way to test component's behavior in response to various DOM interactions? I am aware that you suggest directly accessing a callback functions passed as props to a component and calling them; but if we stick to the principle of testing public api of a component and not peeking inside, what will a test of a DOM-mounted component look like?
@azangru i'm not convinced any convenience method is needed - if you want to invoke the onClick prop, invoke it.
You shouldn't be testing the browser or React itself - and anything related to events is doing just that. Shallow testing is all about peeking inside - you're supposed to test that.
if you want to invoke the onClick prop, invoke it.
Shallow testing is all about peeking inside - you're supposed to test that.
@ljharb I am sorry; I must have expressed myself poorly. Let me try again.
As far as I understand, there are two testing strategies when testing React components.
One is testing a component entirely in isolation. This is purely a unit test and is probably best achieved with shallow rendering. As you say, with shallow rendering you are supposed to peek inside a component, reach for props that you pass to children components, and use them to test the component's functionality. Since children components are not rendered anyway during a shallow test, that's the only practical way of testing callbacks passed to children components.
Of course the drawbacks of shallow testing is that it relies too much on the implementation of React the library; so when React extended its api with hooks, shallow testing was not yet ready for that.
Another strategy is to test the component as well as the whole tree of its children by fully rendering it to the DOM. This is where a unit test borders on (or perhaps becomes) an integration test. This strategy is evangelized by react-testing-library. Using this strategy, you are interacting with the component through the DOM, "as the user would". Which means that to fire a callback in the component you are testing, you want to find the appropriate DOM element and dispatch an event on it.
This second strategy can be achieved in enzyme with full DOM rendering, which would make it possible to use enzyme in the same manner one would use react-testing-library, but still having enzyme's flexible api at one's disposal. It would be very handy, for that kind of testing, to have a convenience method for firing DOM events. I appreciate that such a method will make zero sense for shallow testing; but will make a whole lot of sense for full DOM testing.
@azangru mount's simulate already uses React's TestUtils simulate method, so I believe that's already basically how it operates. I also agree with your description of these two strategies (there are more than two) - but your second strategy is both a) not reliable using jsdom, so you'd need to do it in an actual browser to test properly, so that's where something webdriver-esque should come in; and b) not sufficient to cover server-rendering, which is very important for a11y and robustness.
Separately, you can always find an HTML element on a mount wrapper, and element.getDOMNode().dispatchEvent(…) directly - does that not meet the need you described?
@ljharb thank you for your explanation; I wasn’t aware how mount’s simulate works. Just to clarify though, when you talk of deprecating simulate, do you mean this both for ShallowWrapper and ReactWrapper, as now stated in the docs updated in the PR linked to this issue? I completely understand the reasoning behind removing simulate from ShallowWrapper; but since, as you say, ReactWrapper’s simulate is a proxy to React’s TestUtils’ simulate, doesn’t it justify keeping simulate on ReactWrapper?
@azangru v4 decisions haven't been made since v4 is a long way off. In general, I want to keep as close parity as possible between mount and shallow's methods. I agree that the current functionality of mount's simulate might be worth keeping, but i'd probably remove it and add a simulateEvent method, but with more restrictions on what you can call it on - and then i'd want to try to figure out how it could be properly added for shallow.
Most helpful comment
simulateis indeed deprecated; if there's ever a v4 of enzyme it will be removed with prejudice.The reason it's bad is because it's named "simulate" and it takes an event name, but it does not actually faithfully simulate anything. It's based on the historic implementation of the same method in the shallow renderer which suffers from the same problems. All it does is transform the event name to a prop name, and invoke the function in that prop.
The only way a replacement would be worth considering is if it actually simulated the event - ie, simulating "change" produced a stream of events, including keypress/keydown/keyup/etc, input, change, blur, whatever made sense - and invoked all the expected handlers as a result. This is a very large project, however, and I don't think the knowledge even exists in a single package in the entire ecosystem yet to encapsulate it (it's only in browsers, and not something that could be referenced in this direction).
A PR to make this clear in the docs would be welcomed.