How to unit test asynchronous code for JavaScript in 2020

Team: Igniter from Houston Inc
Houston I/O
Published in
5 min readJul 2, 2020

--

The text you are about to read describes an imaginary dialog between two programmers on their way to discover the orgastic pleasures of a library called asyncFn.

asyncFn provides additional methods to eg. jest.fn or sinon.spy to introduce “late resolve” for the promises returned by mock functions. This simplifies async unit testing by allowing tests that read chronologically, like a story.

From this, we get a new perspective of structuring and writing unit tests.

Foreword

Be warned that we’ll also cover topics such as TDD, pair programming, evil pairing, and “negation testing”. They are not the main topic here, but some words are spared for them nonetheless to justify a way of thinking.

Best of luck, Dear Reader.

Chapter 1: The premise and the foundation

Fry: My name is Fry, and I find it difficult to unit test async-stuff in JavaScript.

Leela: Tell me more.

Fry: Yup, say I wanted to implement something to this specification:

Specification in Gherkin

Fry: The kicker here is that in this game, prompting the player for action is asynchronous.

Leela: Mm-hmm, I think I get it. Let’s get our hands dirty and see where it lands us.

Fry: Here’s an initial test for good measure:

Unit tests so far

Contents of ./monsterBeatdown.js so far:

Production code so far

Leela: Ok, so far so good. I see you chose to inject the mock-function for messaging the player as an argument for the function we are testing. Crystal. Full steam ahead.

Fry: Right on. Next up will be my vision for asking the player if they want to attack, and if not, they lose.

Chapter 2: The problems emerge

Unit tests so far

Contents of ./monsterBeatdown.js so far:

Production code so far

Leela: Hold the phone. I see two problems here. One is the duplication, and the other one is about the test describing occurrences in non-chronological order, making the test harder to read. Let’s start by fixing the first problem.

Chapter 3: The clumsy fix

Fry: Uh, I was just about to remove the duplication anyway. Red-green-refactor, right?

Leela: Right.

Unit tests so far

Contents of ./monsterBeatdown.js so far:

Production code so far

Fry: There. Now the duplication for the test setup has been removed, but only to a degree. However, the other problem of test not being written in chronological order prevents us from removing all the duplication. See how we are forced to repeat game.encounterMonster() because askPlayerToHitMock needs to know how to behave before it is called.

Worse yet, this makes the “describe” dishonest, as it claims to display how "given a monster is encountered", but in reality, this is something that happens only later in the test setup, if ever.

All this kind of bums me out, and I’ve felt the pain of this getting out of hand as in real-life requirements and features start to pile up. I know many ways to ease the pain a little bit, but nothing I’ve tried has left me comfortable.

Leela: I see. Let’s now introduce asyncFn as an expansion to the normal jest.fn to tackle all.

Behold.

Chapter 4: The beholding of asyncFn

Unit tests so far

Contents of ./monsterBeatdown.js so far:

Production code so far

Leela: Now the duplication is gone, and everything takes place in clean, chronological order. It kind of reads like a story, don’t you think?

Fry: Mm-hmm. I see it. I have a good feeling about it. But something is bothering me with the production code. I see that the tests are all green, but clearly, the code does not do anything sensical. It just blows through, merely satisfying the tests.

Leela: You got me there. We are, in fact doing something called “evil pairing”. If you want to mold the production code your way, you need to order it from the universe by writing a test first.

Let me show you how.

Chapter 5: Evil is good and negative is positive

Unit tests so far

Contents of ./monsterBeatdown.js so far:

Production code so far

Leela: There. I’ve reduced the “evilness” of the code by adding something we call “negation tests”. By writing tests that describe what is not supposed to happen we forced the production code to make a little bit more sense.

And if you are wondering where the name “evil pairing” comes from, it comes from playing unit testing ping-pong in such a way that the production code is always written in the most evil way possible, ie. the code is non-sensical for real-life, yet it still satisfies the tests. The only way out of this is to force sense in production through unit tests.

Sometimes you can tell coders are evil pairing, from the evil laughter.

In general, this “evil pairing -mentality” helps produce code that is very robust for the sake of refactoring, and also allows programmers to hone their TDD-mojo a little bit more.

But I digress. However important this may be, it is slightly off-course. What is relevant is that asyncFn supports evil pairing as a line of thinking.

Let’s motor on.

Chapter 6: The multiplicity challenge and enlightenment

Fry: For sure. Can we see how the game develops some steps further? Particularly, I’ve been suffering from testing of functions that are called multiple times, but still, return promises.

Leela: I feel you. My guess is an asyncFn-mock will get called multiple times soon enough, when we start attacking the monster multiple times.

Let’s see how that pans out.

Final form of unit tests

Contents of ./monsterBeatdown.js so far:

Final form of monsterBeatdown.js

Leela: There. Now the monster gets hit multiple times, all while things still happen in a clear order. Our work is done here.

Fry: Color me enlightened. This has changed my view of the world as a programmer and a human being. I shall make sacrifices in your honor and get an asyncFn-tattoo. My grandchildren will know your name.

Summary

Having witnessed the adventure, we’ve seen how the late resolve of asyncFn permitted troublesome tests, with awkward readability, to be refactored orderly as a story, with significantly better readability.

asyncFn is something that we, as a team, have used for multiple years now. It has categorically changed the way we approach unit testing of asynchronous code in JavaScript. Which we so adore.

See more about asyncFn for jest or sinon.

Who are we?

asyncFn is lovingly crafted by Your pals at Team: Igniter from Houston Inc. Consulting.

We are a software development team of friends, with proven tradition in professional excellence. We specialize in holistic rapid deployments without sacrificing quality.

Come say hi at Gitter, email us, or check out the team itself. We just might be available for hiring ;)

--

--