How to Mock the async
NPM Library Using Jest (With Examples)
As a senior developer, one of the first things I teach my team is this: "Write tests like your CI/CD pipeline depends on it—because it does." When working with legacy or third-party libraries like async
, mocking them correctly with Jest becomes essential for writing clean and deterministic unit tests.
In this post, I’ll show you how to mock the async
NPM package using Jest, walk through real-world examples, and explain a few caveats I’ve learned along the way.
🔍 Why Mock the async
NPM Module?
async
is a utility module that provides straight-forward, powerful functions for working with asynchronous JavaScript. Popular functions include:
async.series()
async.parallel()
async.waterfall()
If you're testing a function that relies on async.series()
(or similar), you probably don't want to test the behavior of the library—you want to test your code’s response to the outcome. That’s where mocking comes in.
🧪 Setting Up Jest for Mocking
Before mocking, make sure you have Jest installed:
npm install --save-dev jest
Also install async
if it's not already:
npm install async
✅ Example: Mocking async.series()
with Jest
Let’s say you have a module like this:
// taskRunner.js
const async = require('async');
function runTasks(callback) {
async.series([
function (cb) {
setTimeout(() => cb(null, 'Task 1'), 100);
},
function (cb) {
setTimeout(() => cb(null, 'Task 2'), 100);
}
], callback);
}
module.exports = { runTasks };
You want to test the behavior of runTasks()
without waiting for the real timeouts.
🧩 Here’s how you mock async.series()
:
// __tests__/taskRunner.test.js
jest.mock('async', () => ({
series: jest.fn()
}));
const async = require('async');
const { runTasks } = require('../taskRunner');
describe('runTasks', () => {
it('calls async.series with correct arguments', () => {
const mockCallback = jest.fn();
async.series.mockImplementation((tasks, callback) => {
callback(null, ['Mock Task 1', 'Mock Task 2']);
});
runTasks(mockCallback);
expect(async.series).toHaveBeenCalledTimes(1);
expect(mockCallback).toHaveBeenCalledWith(null, ['Mock Task 1', 'Mock Task 2']);
});
});
⚠️ Gotchas When Mocking async
-
Ensure you mock before importing the module under test. If
async
is imported and cached before mocking, it won’t work. -
Use
jest.requireActual
if partial mocking is needed:
jest.mock('async', () => {
const original = jest.requireActual('async');
return {
...original,
series: jest.fn()
};
});
- Avoid mocking too deeply. Only mock what you need; otherwise, you may introduce noise in your tests or hide bugs.
💡 Pro Tips from a Senior Dev
- Prefer dependency injection if you plan to mock frequently-used libraries. This makes your code more testable and modular.
- Use spies if you want to mock specific behavior but retain original functionality.
- Write integration tests that use the real
async
module as well, so you're not mocking away all your coverage.
🔚 Conclusion
Mocking async
with Jest is straightforward once you understand how module mocking works. It’s a powerful pattern that can save you from flaky tests and speed up your test suite dramatically.
TL;DR:
- Use
jest.mock('async')
- Replace
async.series()
(or any other method) with a mock implementation - Test how your code responds, not the library itself