Skip to content

Instantly share code, notes, and snippets.

@sktse
Last active April 4, 2017 15:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sktse/543f0a7103b4d6272c142030818a7e89 to your computer and use it in GitHub Desktop.
Save sktse/543f0a7103b4d6272c142030818a7e89 to your computer and use it in GitHub Desktop.
How to test asynchronous code

How to test asynchronous code

Purpose

The purpose of this document is to outline the following

  • Why asynchronous tests can pass even though it is not testing anything
  • How to actually test asynchronous code

Why do asynchronous tests result in false passes

  • In one word: javascript
  • Javascript is an asynchronous, non-blocking language.
  • This means that it keeps going and waits on no function.

No breaks!

Javascript + Testing framework

  • So what does your unit test actually do?
  • It runs a block of code and looks for failures.
  • In the absence of failures, your unit test has passed.
  • But javascript is non-blocking! And you are testing asynchronous code.
  • Ideally, this is the behaviour you want:
EXPECTED BEHAVIOUR
|
Main thread
|
| --> Unit test
|         |
|         | --- Async call -> |
|         |                   |
|         | <-- Response ---- |
| <------ |
|
  • But this flow requires the unit test to wait for the response to come back.
  • Javascript will run to the end of the unit test without waiting for a response.
REALITY
|
Main thread
|
| --> Unit test
|         |
|         | --- Async call -> |
|         |                   |
|         |                   |
| <------ |                   |
|                             |
|       ??? <-- Response ---- |
|
  • The response came back after the unit test has finished executing.
  • Therefore, the unit test never tested the response.
  • In the absence of checking the response, the unit test never threw a failure.
  • As a result, this unit test has resulted in false pass.

Oh no!

How to fix this

  • What you want to do is wait for the asynchronous response to come back and finish the unit test only after checking the response.
  • Let's use an example of asynchronous test that does not wait for the response
...
  // THIS DOES NOT CORRECTLY TEST THE RESPONSE
  it('returns success', () => {
    dispatch(actions.goPewPew({
      dependencies: {
        ...Dependencies,
        waveClient: MockWaveClient('tiger-pistol', 'shoot', payload),
      },
    })).then(() => {
      expect(store.getActions()).toEqual(
        [{ type: actionTypes.REACH_FOR_THE_SKY }],
      );
    });
...
  • Jasmine (and Chai as well) has an optional done() callback parameter in the it() and beforeEach() blocks.
  • The callback done() makes the unit test thread stay alive until done() is called.
  • If it is never called, there is a default timeout and the unit test will fail with a specific timeout error.
  • The done() callback is powerful because you can put it in the response to an asynchronous call.
...
  // All fixed now!
  it('returns success', (done) => {
    dispatch(actions.goPewPew({
      dependencies: {
        ...Dependencies,
        waveClient: MockWaveClient('tiger-pistol', 'shoot', payload),
      },
    })).then(() => {
      expect(store.getActions()).toEqual(
        [{ type: actionTypes.REACH_FOR_THE_SKY }],
      );
      done(); //<=== The unit test is not finished until it reaches here!
    });
...
  • Now the unit test thread will wait until the done() is called in the then() block.
  • In addition, this unit test is more robust because it will fail if a response never came back. It would timeout because done() was never called.

How to tell if you have an async call in your test

  • If you have a then() block in your test, you are making an asynchronous call.
  • Even if you mock the response/client/magic out, the test still needs to have the done() callback.

TL;DR

  • Jasmine has a optional callback parameter for it() called done().
  • Add it to your it() function callback.
  • Call it in your asynchronous response.

Conclusion

  • I hope this has helped you figure out how to write unit tests for asynchronous blocks of code Learning is half the battle
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment