Mocking Events in Node.js with EventEmitter and Test Double
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:

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.jsWe 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:

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:

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.
