React-testing-library: Why does `flushPromises` work the way that it does

Created on 21 Mar 2018  路  5Comments  路  Source: testing-library/react-testing-library

From https://twitter.com/Dru89/status/976472959345815552

I've seen examples before where flushPromises is written basically the way that you say:

const scheduler = typeof setImmedate === 'function' ? setImmediate : setTimeout;

export function flushPromises() {
  return new Promise(res => scheduler(res));
}

What I don't really understand is _why_ this actually works. I'm assuming it has something to do with scheduling and how JavaScript _actually_ handles asynchronous published behavior.

And relatedly:
Is this a "hack" that only works because of an implementation detail in Node/V8? (Would this break in a browser or in something like Chakra?) If it's not something in the spec, is it something that's likely to change?

documentation

Most helpful comment

Not an expert here, but I'll give it a try:

I think it works because setImmediate is set to execute the given callback immediately after the browser finishes flushing all the pending work in the queue. It says it'll execute immediately, but it's not actually like simply executing it in the same block as setImmediate function body. It is still scheduling it. It is sometimes replaced with setTimeout(fn, 0) which also does not really execute the given function synchronously, but with as little a delay as possible.

If there are promise callbacks already in javascript's queue pending to be processed at the time setImmediate is called, then these will be processed before the callback given to setImmediate is called. Therefore the new promise being generated in this library's flushPromises is guaranteed to resolve after the pending promises are resolved.

As to wether this might break in the future, if you read the setImmediate documentation it might seem so due to that warning at the beginning, but since it says that node.js supports it, guess that's ok for a test library meant to be run in a node env. Worst case scenario a polyfill can me made using setTimeout(fn, 0).

This article can help understand the even loop in general. And in particular, look for the section titled "Zero delays", which talks about setTimeout with delay zero.

All 5 comments

Not an expert here, but I'll give it a try:

I think it works because setImmediate is set to execute the given callback immediately after the browser finishes flushing all the pending work in the queue. It says it'll execute immediately, but it's not actually like simply executing it in the same block as setImmediate function body. It is still scheduling it. It is sometimes replaced with setTimeout(fn, 0) which also does not really execute the given function synchronously, but with as little a delay as possible.

If there are promise callbacks already in javascript's queue pending to be processed at the time setImmediate is called, then these will be processed before the callback given to setImmediate is called. Therefore the new promise being generated in this library's flushPromises is guaranteed to resolve after the pending promises are resolved.

As to wether this might break in the future, if you read the setImmediate documentation it might seem so due to that warning at the beginning, but since it says that node.js supports it, guess that's ok for a test library meant to be run in a node env. Worst case scenario a polyfill can me made using setTimeout(fn, 0).

This article can help understand the even loop in general. And in particular, look for the section titled "Zero delays", which talks about setTimeout with delay zero.

That was a great explanation @gnapse! I should note also that this is only effective in your tests if you've mocked out your async requests to resolve immediately (like the axios mock we have in our tests).

I'm not worried about setImmediate breaking ever, so I don't think we have anything to worry about.

Anyone wanna add this to the FAQ?

@kentcdodds I'll do it!

I too thought about this being based on ajax requests being mocked, as they otherwise would not be immediately in the queue. I forgot to mention it though. Thanks for clarifying that.

Thanks all! This tracks with what I assumed, but it's good to see it written down and I think talking about why it worksin libs that expose it is probably good. :)

Also the bit about async stuff needing to resolve immediately bis pretty important. I was somehow assuming that it would it would work for all async calls but had no clue how. I suppose for end to end testing, you'd probably still need some way to wait for whatever result you were looking for to occur. (Element on the page, row change in a database, etc.)

For end-to-end tests, check out cypress. It has an amazing way to declare the network requests you expect to occur in your test, and then declaratively wait for them to resolve before continuing the test.

In that case the network requests can either be mocked or not, and the test will work anyway.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bdauria picture bdauria  路  4Comments

julienw picture julienw  路  4Comments

jalvarado91 picture jalvarado91  路  3Comments

drwpow picture drwpow  路  4Comments

good-idea picture good-idea  路  4Comments