A simple way to stub default-only dependencies. This project currently only works for stubbing the default export of a module.
We utilize dependency injection, so stubbed modules cannot be used until time of test (not at import time). For more info, see step 4 in the instructions below.
import runStubbed from 'dce-stub';
In your spec:
import * as myDependency from '...';
...
In the module you are testing:
import * as myDependency from '...';
...
// When using the dependency, use the .default property:
myDependency.default...
...
Call runStubbed
with two arguments:
- replacements
object|object[]
– either one or many stub replacements, each of the form{ dep, stub }
wheredep
is the dependency andstub
is the stubbed version of the dependency - test
function
– tests to run while the dependency is stubbed (dependencies are restored once the function finishes). Function may be asynchronous or synchronous
describe('My Feature', () => {
it('Does Something', async () => {
...
// Define stub replacement
const replacement = {
dep: myDependency,
stub: <stub of myDependency>,
};
// Run the
await runStubbed(replacement, async () => {
...
});
});
});
If you have more than one dependency to stub, just use a list:
...
// Define stub replacements
const replacements = [
{
dep: myDependency,
stub: <stub of myDependency>,
},
{
dep: secondDepencency,
stub: <stub of secondDependency>,
},
...
];
// Run the
await runStubbed(replacements, async () => {
...
});
We use dependency injection, which means that the stubbed version of the dependency is not injected at import time. Instead, it is injected when the test runs.
Don't configure/initialize/use stubbed libraries until time of test:
Example: we are using a library lms
that must be initialized before being used.
Wrong way:
import * as lms from 'my-lms'; const api = lms.default.getAPI(); class Course { constructor(id) { this.id = id; } listStudents() { return api.course.listStudents(this,id); } ... }
Right way:
import * as lms from 'my-lms'; class Course { constructor(id) { this.id = id; this.api = lms.getAPI(); } listStudents() { return this.api.course.listStudents(this,id); } ... }
Another right way:
(if there is no cost to re-initializing over and over)
import * as lms from 'my-lms'; class Course { constructor(id) { this.id = id; } listStudents() { const api = lms.getAPI(); return this.api.course.listStudents(this,id); } ... }
Note: One concrete example of such a library that can be re-initialized with no cost, is caccl/client/cached
We have two helpers:
getNameFromServer
– pulls the current user's first name from the servergenIntro
– a script that callsgetNameFromServer
and generates a welcome message
Our goal is to write a unit test for genIntro
, isolating it from getNameFromServer
. Thus, we want to import genIntro
while replacing getNameFromServer
with a fake stubbed version of that module.
export default async () => {
return sendRequest({
method: 'GET',
url: 'https://host.com/user/profile/name',
});
};
import * as getNameFromServer from './getNameFromServer';
export default async () => {
// Get the current user's name
const name = await getNameFromServer.default();
// Create the message
return `Hi ${name}! It is a pleasure to meet you.`;
};
import runStubbed from 'dce-stub';
// Import module to test
import genIntro from './genIntro';
// Import dependencies we want to stub
import * as getNameFromServer from './getNameFromServer';
// Create the getNameFromServer stub
const getNameFromServerStub = () => {
return 'Divardo';
};
// Tests
describe('genIntro', () => {
it('Generates a valid introduction', async () => {
// Create stub replacements
const replacement = {
dep: getNameFromServer,
stub: getNameFromServerStub,
};
// Run tests with stub replacements
await runStubbed(replacement, async () => {
// Generate an intro message
const intro = await genIntro();
// Test the intro
assert.equal(
intro,
'Hi Divardo! It is a pleasure to meet you.',
'Invalid intro produced'
);
});
});
});