So a bit of JavaScript 102... The other day we were writing a test to confirm that after a function runs, our store has emitted certain action. In summary:
test('correct actions', () => {
const expectedActions = ...
store.run(LOAD);
const actualActions = store.getActions();
expect(actualActions).toEqual(expectedActions);
});
LOAD
will actually trigger some asynchronous functionality. Specifically, it
will read from device storage. There is no way to hook into this since this
flow is controlled by a store middleware, i.e we can't do store.run().then
or something like that.
This test fails because the expect
is run before the asynchronous action
completes.
We can rewrite this test in a way so that we pass a message to the event queue (do something asynchronous) in the test itself. So as an example:
test('somethin', () => {
const expected = 2;
let actual = 1;
run = () => actual++;
setImmediate(run);
expect(actual).toEqual(expected);
});
This test will fail for the same reason -- because of "run to completion." In
particular the expect
actually runs before run
, so actual
isn't
incremented early enough.
In order to fix this, we can write it like this:
test('somethin', () => {
const expected = 2;
let actual = 1;
run = () => actual++;
setImmediate(run);
check = () => expect(actual).toEqual(expected);
setImmediate(check);
});
That's because check
runs after run
since it was passed to the event
queue after run
. Our event queue will look like this:
[
main, // main actually adds `run` and `check` to the queue
run,
check,
]
We can adapt this to our original test:
test('correct actions', () => {
const expectedActions = ...
store.run(LOAD);
const actualActions = store.getActions();
setImmediate(() => expect(actualActions).toEqual(expectedActions));
});
For the same reason, the asynchronous action from run
will run before the
setImmediate
in the test.
Another functionally identical way to write this would be:
test('correct actions', async () => {
const expectedActions = ...
store.run(LOAD);
const actualActions = store.getActions();
await new Promise(resolve => setImmediate(resolve));
expect(actualActions).toEqual(expectedActions);
});
If you're unfamiliar with async
/await
, everything after await
can be
considered a separate item passed to the event queue when awaiting something
async such as setImmediate
which is why this functions the same.
Unfortunately we can't simply use await Promise.resolve()
because this
actually runs synchronously, i.e. no additional message gets passed to the
queue.
Our queue from above looks like this:
[
main,
async_action_from_run,
setImmediate_from_test,
expect
]