There are 2 mocha test files:
Both of these servers initialize a database connection.
When ran independently both of them pass, when ran together the second one fails with the following error:
Error ConnectionManager.getConnection was called after the connection manager was closed
Looking at the console, connection with the database is established 2 times for each test, but still acts as a single pool.
# db/index.js
global.TABLE_USERS = 'users';
const Promise = require('bluebird');
const Sequelize = require('sequelize');
const config = require('./../config');
const User = require('./User');
/**
* @return {Promise}
*/
const connect = () => {
return new Promise((resolve, reject) => {
let sequelize = new Sequelize(config.postgres.database, config.postgres.user, config.postgres.password, {
host: config.postgres.host,
dialect: 'postgres',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
define: {
underscored: false,
freezeTableName: false,
charset: 'utf8',
dialectOptions: {
collate: 'utf8_general_ci'
}
},
});
let user = User(sequelize);
sequelize
.authenticate()
.then(() => {
resolve({
User: user,
sequelize: sequelize
})
})
.catch(err => {
console.error('Couldn\'t authenticate');
reject(err)
})
});
};
module.exports.connect = connect;
Main server module:
const express = require('express');
const bodyParser = require('body-parser');
global.Promise = require('bluebird');
let routing = require('./routing');
const config = require('./config');
const middleware = require('./middleware');
let database = require('./db');
let Repositories = require('./repositories');
let Services = require('./services');
let Controllers = require('./controllers');
const Promise = require('bluebird');
/**
* @property {http.Server} this.app
*/
class Server {
constructor() {
this.app = express();
}
/**
* @param {Function} beforeHook
*
*/
init(beforeHook = null) {
return this._initDatabaseConnection()
.then(() => {
this._initContainer(beforeHook);
this._initRoutes();
return this._initServer()
});
}
/**
*
* @param {Function} beforeHook
* @private
*/
_initContainer(beforeHook) {
this.container = {};
// Modify for testing before starting
if (typeof beforeHook === 'function') beforeHook(this);
this.container = Repositories(this.database);
this.container = Services(this.container);
this.controllers = Controllers(this.container);
}
/**
*
* @private
*/
_initRoutes() {
this.app.use(bodyParser.json());
middleware.handleCors(this.app);
this.app.use(routing({...this.controllers, ...this.services}));
middleware.handleErrors(this.app);
}
/**
*
* @private
*
* @return {Promise}
*/
_initServer() {
return new Promise((resolve, reject) => {
this.server = this.app.listen(config.app.port, () => {
console.log(`Server started listening in ${config.app.env} on port ${config.app.port}`);
resolve(this)
});
});
}
/**
*
* @return {Promise}
* @private
*/
_initDatabaseConnection() {
return database.connect()
.then(connection => {
this.database = connection;
console.log('Connected to the database');
return Promise.resolve()
})
}
/**
* @return {Promise}
*/
close() {
this.server.close();
return this.database.sequelize.close();
}
}
module.exports = Server;
First test case
const assert = require('assert');
const chai = require('chai'),
expect = chai.expect,
chaiHttp = require('chai-http');
chai.use(chaiHttp);
const Server = require('../../src/Server');
describe('Server app test', () => {
let server;
before(async () => {
server = await (new Server()).init();
});
after(async () => {
await server.close();
});
it('should say respond it\'s name', async () => {
let pingServer = () => {
return new Promise((resolve, reject) => {
chai.request(server.server)
.get('/')
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(200);
resolve(res.body)
});
});
};
let res = await pingServer();
assert.equal(res.msg, 'API server');
});
});
Second test case, UserControllerTest
const assert = require('assert');
const chai = require('chai'),
expect = chai.expect,
chaiHttp = require('chai-http');
chai.use(chaiHttp);
const sinon = require('sinon');
const Promise = require('bluebird');
const Response = require('./../../src/lib/RequestHelper');
const UserValidation = require('./../../src/validation/UserValidation');
const Server = require('./../../src/Server');
const ReCaptchaService = require('./../../src/services/ReCaptchaService');
const ValidationError = require('./../../src/errors/ValidationError');
describe('/users/signup', () => {
describe('valid reCaptcha scenario', () => {
let server, reCaptchaServiceStub;
before(async () => {
reCaptchaServiceStub = sinon.stub(ReCaptchaService.prototype, 'authenticate').returns(true);
function setReCaptchaServiceStub(server) {
server.services = {ReCaptchaService: new reCaptchaServiceStub()};
}
server = await (new Server()).init(setReCaptchaServiceStub);
});
after(async () => {
reCaptchaServiceStub.restore();
await server.database.User.destroy({where: {}});
await server.close();
});
beforeEach(async () => {
await server.database.User.destroy({where: {}});
});
it('should allow user to register', async () => {
let data = {email: '[email protected]', password: '1234'};
data[UserValidation.CAPTCHA_RESPONSE] = 'captcha_token';
let signUp = (data) => {
return new Promise((resolve, reject) => {
chai.request(server.server)
.post('/users/signup')
.send(data)
.end((err, res) => {
console.log(res.body)
expect(err).to.be.null;
expect(res).to.have.status(Response.STATUS_OK);
resolve(res.body)
});
});
};
let res = await signUp(data);
expect(res.token).to.be.a('string');
});
});
describe('invalid reCaptcha scenario', () => {
let server, reCaptchaServiceStub;
before(async () => {
reCaptchaServiceStub = sinon.stub(ReCaptchaService.prototype, 'authenticate')
.onCall()
.throws(new ValidationError('some err'));
function setReCaptchaServiceStub(server) {
server.container.ReCaptchaService = new reCaptchaServiceStub()
}
server = await (new Server()).init(setReCaptchaServiceStub);
});
after(async () => {
reCaptchaServiceStub.restore();
await server.close();
});
beforeEach(async () => {
await server.database.User.destroy({where: {}});
});
it('should send a bad request on invalid reCaptcha', async () => {
let data = {email: '[email protected]', password: '1234'};
data[UserValidation.CAPTCHA_RESPONSE] = 'random_token';
let signUp = (data) => {
return new Promise((resolve, reject) => {
chai.request(server.server)
.post('/users/signup')
.send(data)
.end((err, res) => {
expect(err).to.not.be.null;
expect(res).to.have.status(Response.STATUS_BAD_REQUEST);
resolve(res.body);
});
});
};
let res = await signUp(data);
expect(res.err).to.equal(UserValidation.ERR_INVALID_RECAPTCHA);
});
});
});
After doing more research into this, this is the following behaviour that caused the issue.
When mocha is ran to test the files recursively it is ran as a single process, this causes conflicts when closing the connection with sequelize.
To avoid this issue you should NOT close the connection with sequelize, but instead set an extra option with mocha --exit which terminates any additional cycles in the event loop after tests complete, thus closing the sequelize connection by itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With