Mocking Events in Node.js with EventEmitter and Test Double

Spike Burton
Nov 1 · 3 min read
Photo by Émile Perron on Unsplash

Writing unit tests for asynchronous non-deterministic code can be tricky. How do you go about testing functions that make use of network calls or read from/write to the filesystem? Mocking libraries like testdouble or sinon can help. But what about testing these sorts of scenarios that make use of Node’s event loop? Consider the following example:

Asynchronous code making use of the event loop

In order to test this function and cover all the bases, we need to be able to mock the connect, error and timeout events. In order to do this, we need to stub the connect and setTimeout methods. We can accomplish this by making use of an EventEmitter and stubbing the methods by attaching them to the emitter.

Let’s set up our test rig:

npm init -y
npm install -D mocha chai testdouble
mkdir test && touch test/ping.js

We also want to configure the test script in package.json as follows:

"scripts": {
"test": "mocha test/*"
},
...

Next, let’s set up and discuss the configuration for our tests:

Test set up and configuration

There’s a lot happening here. First, we are calling td.replace() on the net module, before requiring the code we wish to test. This is crucial, and is an odd quirk about testdouble and also the way that Node caches required modules. The replace method is similar to the built-in require method, except it stubs all of the code in that module.

Next, we are setting up some hooks that run before and after each test inside the describe block. In the beforeEach hook, here is where we create our “client”. In order to mock net.Socket, we can simply create an event emitter and attach stubbed methods directly to it. This gives us the ability to emit the events being listened for while faking the connect and setTimeout methods. When our code requests a new socket object, we will simply fake it out by returning this custom client object. This is precisely what the td.when(new net.Socket()) call inside our hook does. We also need to remove all event listeners when the “client” is destroyed. Finally, after each test runs, we need to reset testdouble by calling td.reset() to prevent test pollution.

Let’s write our first test:

Successful connection test case

First, we are stubbing the client.connect method by matching any inputs and simulating a successful connection by emitting a connect event. We are then calling our ping method, awaiting the response, and asserting that the response resolves to true. The catch block ensures that we handle the case when the Promise rejects on an error, if the error event were emitted.


I’ll leave it to you to handle the other base cases of when there is a timeout or an error 😉. If you’d like, you can check out my code here.

Spike Burton

Written by

Observer.Thinker.Creator.Destroyer.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade