I'm writing unit tests for an API that uses MongoDB, with mongoose being used to access the database.
I have a model file which defines and exports a mongoose model:
const { Schema, model } = require('mongoose');
const TravelSchema = new Schema({
staffFirstName: {
type: String,
required: true,
},
staffLastName: {
type: String,
required: false,
},
kmTravelled: {
type: Number,
required: true,
},
});
module.exports = model('travel', TravelSchema);
I then have a method that takes a req and res, constructs an object out of the attributes of the req.body, then saves it to the database:
const TravelSchema = require('../models/travel_usage');
exports.submitTravelUsage = async function (req, res) {
try {
var data = {
staffFirstName: req.body.staffFirstName,
staffLastName: req.body.staffLastName,
kmTravelled: req.body.kmTravelled,
}
var travelUsage = new TravelUsage(data);
travelUsage.save();
return res.status(201).json(data);
}catch(error){
console.log(error.message);
return res.status(400).json({message: error.message});
}
My test file for the backend method looks something like:
const request = require('supertest');
const app = require('../src/app');
const sinon = require('sinon');
const assert = require('assert').strict;
const TravelSchema = require('../models/travel_usage');
describe('POST /travelUsage - returns 201', function () {
it('responds with 201 created - basic POST body', function (done) {
this.timeout(7500);
var save = sinon.spy(TravelSchema, 'save');
request(app)
.post('/travelUsage')
.set('Content-Type', 'application/json')
.send(travelUsageData)
.then(function (response) {
assert.equal(response.status, 201);
sinon.assert.calledOnce(save);
done();
})
.catch(err => done(err));
})
})
But when the test is run an error is thrown saying TypeError: Cannot stub non-existent property save
. It seems that the model object exported by the model file doesn't contain the save()
method, but when a new model is created with data it gains the save()
method, so is there a better way to spy on the save method being called in the submitTravelUsage method?
CodePudding user response:
There is no .save()
method on mongoose schema. A model instance and A document have the .save()
method. Please read the Models doc.
So you should stub the .save()
method on the mongoose document(a model instance).
And your code is a commonJS version, so we will use proxyquire to help us stub the default exports(module.exports = model('travel', TravelSchema);
)
Besides, we don't need to use the assert
built-in module of Node.js. Sinon.js already provides a set of assertions
E.g.
./models/travel_usage.js
:
const { Schema, model } = require('mongoose');
const TravelSchema = new Schema({
staffFirstName: {
type: String,
required: true,
},
staffLastName: {
type: String,
required: false,
},
kmTravelled: {
type: Number,
required: true,
},
});
module.exports = model('travel', TravelSchema);
app.js
:
const express = require('express');
const TravelUsage = require('./models/travel_usage');
const app = express();
app.use(express.json());
app.post('/travelUsage', function (req, res) {
try {
var data = {
staffFirstName: req.body.staffFirstName,
staffLastName: req.body.staffLastName,
kmTravelled: req.body.kmTravelled,
};
var travelUsage = new TravelUsage(data);
travelUsage.save();
return res.status(201).json(data);
} catch (error) {
console.log(error.message);
return res.status(400).json({ message: error.message });
}
});
module.exports = app;
app.test.js
:
const request = require('supertest');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('POST /travelUsage - returns 201', function () {
it('responds with 201 created - basic POST body', function (done) {
const travelUsageDocumentStub = {
save: sinon.stub(),
};
const TravelUsageModelStub = sinon.stub().returns(travelUsageDocumentStub);
const app = proxyquire('./app', {
'./models/travel_usage': TravelUsageModelStub,
});
const travelUsageData = {
staffFirstName: 'a',
staffLastName: 'b',
kmTravelled: 'test',
};
request(app)
.post('/travelUsage')
.set('Content-Type', 'application/json')
.send(travelUsageData)
.then(function (response) {
sinon.assert.match(response.status, 201);
sinon.assert.match(response.body, travelUsageData);
sinon.assert.calledWithExactly(TravelUsageModelStub, travelUsageData);
sinon.assert.calledOnce(travelUsageDocumentStub.save);
done();
});
});
});
Test result:
POST /travelUsage - returns 201
✓ responds with 201 created - basic POST body (2302ms)
1 passing (2s)
------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------------|----------|----------|----------|----------|-------------------|
All files | 93.94 | 100 | 100 | 93.55 | |
73767036 | 92.86 | 100 | 100 | 92.86 | |
app.js | 84.62 | 100 | 100 | 84.62 | 18,19 |
app.test.js | 100 | 100 | 100 | 100 | |
73767036/models | 100 | 100 | 100 | 100 | |
travel_usage.js | 100 | 100 | 100 | 100 | |
------------------|----------|----------|----------|----------|-------------------|
package versions:
"express": "^4.17.1",
"mongoose": "^5.11.9",
"sinon": "^7.5.0",
"proxyquire": "^2.1.3",
"supertest": "^4.0.2"