2.4 Stub ES6 Class “extend”
Historically, JavaScript has used protypal inheritance and constructor functions to deliver application code in an object-oriented fashion.
ES6 has introduced the JavaScript class
keyword. To some extent JavaScript classes are syntactic sugar over the JavaScript prototype.
This poses a new problem for testing, how does one test a JavaScript class that extends
another. This is known as inhertances in object-oriented programming circles.
The previous section has shown how one might test a global class’ invocation, this section will look at how to do the same for inheritance.
Stub ES6 class inheritence (class Foo “extends” Bar)
This section goes through some patterns that can be used to unit test ES6 classes.
The examples will use Jest module auto-mocking but should be portable to other module mocking libraries (eg. Proxyquire) with some modifications.
We’ll be testing the following model (which is an ES6 class):
const { Model } = require('sequelize');
class MyModel extends Model {
static init() {
return (
// Config
super
.init()
);
}
isAvailable(date) {
if (!Array.isArray(this.meetings)) {
throw new TypeError('meetings should be eager-loaded');
}
return !this.meetings.some(
({ startDate, endDate }) => startDate < date && endDate > date
);
}
}
module.exports = MyModel;
At the module level, we want to:
- mock/stub out Sequelize (and the
Model
base class) - import the model
In the test:
- Instantiate the model that we’ve defined (without crashing)
- Set some properties on that instance
- Run some methods
- Assert on the output
jest.mock('sequelize');
const Model = require('./02.04-model');
test('It should not throw when passed a model containing an empty list of meetings', () => {
const model = new Model();
model.meetings = [];
expect(model.isAvailable.bind(model, new Date(Date.now()))).not.toThrow();
});
Alternative with Object.assign
If we’re setting more than a single instance property, using Object.assign
can be easier to manage:
// other tests
test('It should not throw when passed a model containing an empty list of meetings', () => {
const model = Object.assign(new Model(), {
meetings: []
});
expect(model.isAvailable.bind(model, new Date(Date.now()))).not.toThrow();
});
The tests both pass as per test output.
npx jest src/02.04-model-mock.test.js
PASS src/02.04-model-mock.test.js
✓ It should not throw when passed a model containing an empty list of meetings (25ms)
✓ It should not throw when passed a model containing an empty list of meetings (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
The key part of both of these is the .bind(model, /* rest of params */)
, this is, as so many other times, not a Jest-specific trick, rather how JavaScript works. .bind
sets the this
context to its first parameter, and the rest of the parameters are passed as arguments to the function that’s being bound.
This section has gone through 2 approaches to testing a class that inherits from another.
The next sections will dive deeper into creating object mocks for tests.