Skip to content

Instantly share code, notes, and snippets.

@ajcrites
Created June 2, 2018 20:08
Show Gist options
  • Save ajcrites/6658f92f85acbcae8258ef3bf8654bb9 to your computer and use it in GitHub Desktop.
Save ajcrites/6658f92f85acbcae8258ef3bf8654bb9 to your computer and use it in GitHub Desktop.

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
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment