-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
setState calls within simulate events (async or not?) #1054
Comments
Events in the browser are sync. |
Right but I'm talking about the state changes that happen due to the event from setState, couldn't they be async? |
Yes, sure, any setState call could be async (primarily in React 16+, I believe) |
Right, so with direct setStates I can use a callback to address that, but setStates in event handlers I can't. However in the example I linked from the docs, it treats the event handler's setState as if it's synchronous. I'm just wondering how the community deals with this, cause it should be a very common issue, tons of event handlers have setStates within them, all of which could be async. I've seen tons of examples both on and off of the docs and none of them seems to be addressing this, just wondering what I'm missing.. |
The issue of sync over async is only an issue for testing, or for "doing things after the setState is done" - the latter is handled by a callback. The former is more complex and depends on your architecture. |
Okay a more specific example, the example I posted from the documentation above:
In this case, the click changes the state and happens to be synchronous, so this test happens to work. But this seems to be by "chance" given that setState can be asynchronous. Would you say that the way that this was written in the simulate docs is not how it should be done given the potentially async nature of setState? |
Personally I think |
Thank you, this helps a bunch. |
@huan-ji could you post a snippet of the solution you went for? I'm looking at a way to test event methods with async handleFocus() {
this.setState(() => ({ active: true }));
} And my test: describe('Events', () => {
const component = shallow(
<Component>
Hello World
</Component>
);
it('sets state to active onFocus', () => {
component.simulate('focus');
expect(component.state('active')).toBe(true);
});
}); |
There's no real way to do that, because React could update the state async. You'd have to make handleFocus take a callback or return a promise, so that your test can know when it should be asserting (and not use simulate at all) |
I got a lot of trouble doing testing with async stuff. @ljharb To me, the handler is an implementation detail. But from your suggestion, we should really avoid using simulate at all? Due to the issue probably caused by the async in react? |
Yes. The problem is that "simulate" doesn't actually simulate anything - it just invokes a prop. So, it's better for your test to explicitly invoke a prop. |
I am very confused about this one, even after reading this thread. The official documentation for simulate suggests a scheme which is definitively unreliable:
Setting state in an event handler has got to be one of the most (if not the most) common use case for an event handler. Changing all event handlers to return a promise in order to fit Enzyme seems like a huge violation of SOC -- why should my product code be written around Enzyme in this way? IMO @ljharb any advice here would be super helpful. Not using simulate/directly invoking the prop doesn't solve the issue of not being able to write tests around handlers if I can't somehow "force sync". |
The answer is “because React and enzyme don’t and can’t expose the internal JS code your functions have done, unless it exposes a way to do so”. In other words, you’re not writing around enzyme - you’re writing around the javascript language itself, which is literally your only option to test that code. |
instead of testing
just test
for example
and then utilize setState's callback:
|
@ljharb I believe enzyme should flush the entire 'click' event queue -- that element probably has a queue of onClick handlers (maybe just a root handler) or an iterator that will specify it's done when the entire stack is exhausted. enzyme should wrap that event queue as part of |
@asyncb that's impossible, because there's no such concept in javascript, and the browser doesn't expose that either. |
@ljharb I'm 99% sure the event queue is deterministic and ordered. the same as |
@asyncb yes but in your enzyme tests, or in enzyme itself, one has no way of knowing if the component code sets a timeout that in turn sets another timeout. There is no way in javascript to know that "everything pending" has flushed - jest provides such a mechanism, but that's because it hijacks all of setTimeout and friends before your user code runs to do it. |
@ljharb This issue is not about other timeouts etc, it's about not knowing whether |
@alexanderkjeldaas that would only be the case synchronously - again, it is impossible to know when setState calls in an unknown future tick have executed, since the test will have already concluded by then. enzyme already hooks into setState in the way you describe, but it can't violate the way the language works. |
this just sounds incredibly stupid to me, and I don't want to be disrespectful, but "you don't understand how the language works" and "you have to account for set timeout if you're testing a react event handler" is not helping similar to cypress, assertions could be automatically retried until a passing state occurs |
@asyncb i'm not trying to be patronizing, but i can assure you, with a significant amount of authority, that this is indeed how the language works. It is utterly impossible for enzyme to handle this without mutating the global environment. Cypress' approach makes sense for integration tests, but enzyme is a unit test framework - retries are not appropriate. |
the react update cycle is deterministic, whether or not you think it's utterly impossible to test subsequent states, and enzyme should either care about testing async code or it should not
unsubscribing from thread |
(Modulo the rage-quit) @ljharb I think ^ this might present a reasonable compromise. Maybe enzyme can't guarantee anything, but why not do the "expected" thing for the vast-majority case? |
Because, as I’ve stated, the language simply doesn’t allow the expected thing to be possible for the majority use case. Literally anything we did here would be rife with assumptions, absolutely would break nondeterministically for nearly everybody. It’s simply not worth it - there’s no getting around that tests can’t magically know what your code is doing. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
* FYR-6498 | Added validate and autoFocus prop to InlineEditor * FYR-6498 | Optimization - removed unnecessary prop * FYR-6498 | Updated the test cases enzymejs/enzyme#823 enzymejs/enzyme#1054 In case of onChange returning resolved Promises, since setState is async, the assertions executed even before setState actually happened.
* FYR-6498 | Added validate and autoFocus prop to InlineEditor * FYR-6498 | Updated the test cases enzymejs/enzyme#823 enzymejs/enzyme#1054 In case of onChange returning resolved Promises, since setState is async, the assertions executed even before setState actually happened. * FYR-6498 | added pre-hook prop for keydown event * FYR-6498 | Added hook for Escape key in InlineEditor
Been researching several issues regarding this, no clear answer from what I can see.
So from what I've read, React setState can be async at times and is not reliably synchronous. I see that a callback to enzyme's setState has been added to address this. However, this does not address the cases where an event handler calls setState. If a wrapper simulates a click that also sets state, couldn't the event also be async if the setState is async?
The reason I bring this up is that all over the documentation examples the potentially async nature of setState is apparently ignored, all examples treat it as synchronous. For example see link below:
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/simulate.md
Is it fine to treat simulated events as synchronous even though they contain setState calls which can be asynchronous? What's the official word on this?
The text was updated successfully, but these errors were encountered: