How to Override Underlying JavaScript Modules?

Overriding functions for testing purpose

I got lost amongst the documentation for various testing frameworks that provide capabilities like spy, fake, mock and stub to support testing, looking for a way to programmatically swap out a module's implementation.

I have been wondering how to test a fully baked system written in JavaScript where deeply subordinate dependencies can be mocked to ensure that, say, database or remote API calls do not occur and instead prepared data is available to the module under test. If what I’m trying to get at here is much easier than appears — and I just need to start thinking about these APIs differently — I could use some guidance.

This article discusses Mocha, though the principals may be applied to other testing frameworks.

Say you have…

Say you have a module that tests makeThingsHappen and, somewhere in the internals of this module's exported function, something called getSomeData is required. This makeThingsHappen module needs to make things happen, as the name implies. However, the things that happen require a database call within getSomeData, which is not something you want to do in a "unit" test. The test then becomes an integration test, exercising areas of the system (e.g. the connection to the database itself) that really are outside the scope of your test's responsibility. Let's keep this "unit"y.

In the underlying function, getSomeData, a database connection occurs, a query follows and the exported function resolves with the retrieved data. In a proper unit test, you can fake, mock and/or spy all kinds of things... But that is only feasible within the the testing scope. So how do you simply override (overload?) getSomeData so your unit tests can skip connection to the database?

Unfortunately, I haven’t found any good pointers on how to simply do this. There is another way, though.

Here’s how to do it…

The good news is that you can accomplish delivery of fixture data by writing your module unit to be “test aware”. You can do this with a simple ternary and a firm decision on your test utility.

Imagine your module returns some database records that you want the ids for and your file is called get-database-data.js.

// get-some-data.js
const { Pool } = require('pg'); // see pg docs
const { sprintf } = require('sprintf-js');
const getDatabaseData = async () => {
// Let's just say `query` is `select * from the_things`
let allTheThings = await pool.query(query)
// `pg` fetch is asynchronous
.then((res) => {
let ids = [];
res.rows.forEach((row) => {
ids.push(row.id.toString());
});
pool.end();
return ids;
})
.catch(function(e) {
console.log('error', sprintf('Failed: %s', e.stack));
});
return allTheThings;
}
module.exports = getDatabaseData;

Now, just adjust get-database-data.js to produce a highly testable situation, simply just add:

const getTestData = async () => {
return require('../some-file-that-exports-test-data');
}

and then…

module.exports = (global.it) ? getTestData : getDatabaseData;

… will do the trick. But what’s global.it?

Well, it’s Mocha adding itself to the global space in the form of ...it. If you're using Chai or Sinon, you'll want to change that to global.chai or global.sinon. Admittedly, writing a module so that it makes assumptions about a particular test runner is a fragile pattern so we just wave our hands and say that the detection of whether a module is running in a test context should be abstracted to a shareable utility. For now, it's just hardcoded in the example for demonstration purposes.

Full file here:

// get-some-data.js
const { Pool } = require('pg'); // see pg docs
const { sprintf } = require('sprintf-js');
const getDatabaseData = async () => {
// Let's just say `query` is `select * from the_things`
let allTheThings = await pool.query(query)
// `pg` fetch is asynchronous
.then((res) => {
let ids = [];
res.rows.forEach((row) => {
ids.push(row.id.toString());
});
pool.end();
return ids;
})
.catch(function(e) {
console.log('error', sprintf('Failed: %s', e.stack));
});
return allTheThings;
}
const getTestData = async () => {
// This file contains your mock data
return require('../some-file-that-has-test-data');
}
module.exports = (global.it) ? getTestData : allTheThings;

Then you just do, like usual, npm test and this occurs...

[time:seconds] user@host:/Path/To/The/Test $ npm test
> this-package-being-tested@0.0.1 test /Path/To/The/Project
> mocha
theNameOfThisUnit()
✓ Test tested something (NNms)
1 passing (NNms)

Conclusion

Mocking data is one level. Mocking functionality is another level. Mocking underlying data and functionality is a bit tricky. In this article, we see that in order to mock functionality and/or data, it is important for modules themselves to be test-aware. In this case, we aren’t overriding so much as we’re just swapping out an implementation behind the scenes.

All that being said, this direction seems fragile and burdensome. It would be wonderful if it were possible when authoring a unit test to provide an alternate implementation for some distant dependency. Like, for example, any time downstream code requires the pg module, the implementation of its ...query() method simply returns {}. It does seem like this is the whole purpose of fake, though. My guess is that I just need to change how I think about the modules I construct so that they lend themselves better to faking methods from within unit tests. Next article, I’ll tackle the subject head on.

In this article, we look at an existing file structure where get-some-data.js has a database accessor that conveys appropriate data to calling code. Since our project owns this module, we modified it so that it knows whether it is executing in a "test" context and, if so, returns fixture data. Now we can test our code with predictable and verifiable success results.