Sequelize连接管理器关闭后调用getConnection的测试问题排查
问题:Sequelize集成测试随机报错
ConnectionManager.getConnection was called after the connection manager was closed! 我在对基于Node.js、Express、Jest和Sequelize的API做集成测试时,随机出现错误:Sequelize ConnectionManager.getConnection was called after the connection manager was closed!。该问题仅出现在用户功能的集成测试中,用Postman调用API完全正常。
我已尝试调整连接池参数、更换数据创建/更新方式,但始终无法解决。测试失败具有随机性,时而正常时而报错,但错误信息一致,调试确认是Sequelize在执行更新或删除操作时触发该错误。
相关代码
用户模型
'use strict'; const { Model } = require('sequelize'); module.exports = (sequelize, DataTypes) => { class User extends Model { } User.init({ id: { type: DataTypes.INTEGER, autoIncrement: true, allowNull: false, primaryKey: true, }, full_name: { type: DataTypes.STRING, allowNull: false, }, email: { type: DataTypes.STRING, allowNull: false, unique: true, }, password: { type: DataTypes.STRING, allowNull: false, }, is_active: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, }, }, { sequelize, modelName: 'User', tableName: 'user', timestamps: false, hooks: { afterCreate: (record) => { delete record.dataValues.password; delete record.dataValues.is_active; } } }); return User; };
用户仓库方法示例
async delete (id) { try { const t = await db.sequelize.transaction(); await User.update( { is_active: false }, { where: {id: id}, transaction: t } ); await t.commit(); } catch (error) { await t.rollback(); throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR,'Error while deleting user'); } };
集成测试代码
const httpStatus = require('../../src/utils/statusCodes'); const request = require('supertest'); const app = require('../../app'); const db = require('../../src/database/models'); const User = db.user; const Token = db.token; describe('Testing user feature', () => { let tempUser; let token; beforeAll(async () => { tempUser = await User.create({ id: 100000, full_name: 'Porra', email: 'porra@gmail.com', password: '$2b$10$OMDQ.q5dkZAZkQH1g5W6IOP4ZLCwBV4xnTCHDng2pNhlWOpq/n5xO', created_at: new Date(), updated_at: new Date(), is_active: true }); const loginResponse = await request(app).post('/login').send({"email": tempUser.email, "password": "1234"}); token = loginResponse.body.token; }); afterAll(async () => { await Token.destroy({ where: { user_id: tempUser.id } }); await User.destroy({ where: { id: tempUser.id } }); await db.sequelize.close(); }); afterEach(async () => { await db.sequelize.closeConnections(); }); it('Should update a user', async () => { const updatedUser = await request(app).put(`/users/${tempUser.id}`).set('Authorization', token).send({ full_name: 'Tadeuzinho Smith' }); expect(updatedUser.status).toBe(httpStatus.OK); expect(updatedUser.body.details).toBe('User updated successfully'); }); it('Should delete a user', async () => { const deletedUser = await request(app).delete(`/users/${tempUser.id}`).set('Authorization', token); expect(deletedUser.status).toBe(200); expect(deletedUser.body.details).toBe('User deleted successfully'); }); });
问题原因
测试代码中afterEach钩子调用了db.sequelize.closeConnections(),会关闭所有当前连接。但Jest默认并行执行测试,第一个测试结束后关闭连接,第二个测试再执行数据库操作时,连接管理器已被破坏,从而触发报错。另外afterAll中的db.sequelize.close()会彻底关闭连接管理器,若测试未完全结束,后续操作也会报错。
修复方案
- 移除
afterEach中的连接关闭操作:不要在每个测试后关闭连接,让连接池复用连接即可。 - 用事务隔离测试数据:替代关闭连接的方式,在
beforeEach启动事务,afterEach回滚事务,既隔离测试数据,又不影响连接池。 - 完善事务异常处理:确保仓库方法中事务未创建时不会调用回滚,避免额外报错。
修改后的测试代码
const httpStatus = require('../../src/utils/statusCodes'); const request = require('supertest'); const app = require('../../app'); const db = require('../../src/database/models'); const User = db.user; const Token = db.token; describe('Testing user feature', () => { let tempUser; let token; let transaction; beforeAll(async () => { tempUser = await User.create({ id: 100000, full_name: 'Porra', email: 'porra@gmail.com', password: '$2b$10$OMDQ.q5dkZAZkQH1g5W6IOP4ZLCwBV4xnTCHDng2pNhlWOpq/n5xO', created_at: new Date(), updated_at: new Date(), is_active: true }); const loginResponse = await request(app).post('/login').send({"email": tempUser.email, "password": "1234"}); token = loginResponse.body.token; }); beforeEach(async () => { // 启动事务用于测试数据隔离 transaction = await db.sequelize.transaction(); }); afterEach(async () => { // 回滚事务,不污染原始数据 await transaction.rollback(); }); afterAll(async () => { // 清理测试数据后再关闭连接 await Token.destroy({ where: { user_id: tempUser.id } }); await User.destroy({ where: { id: tempUser.id } }); await db.sequelize.close(); }); it('Should update a user', async () => { const updatedUser = await request(app).put(`/users/${tempUser.id}`).set('Authorization', token).send({ full_name: 'Tadeuzinho Smith' }); expect(updatedUser.status).toBe(httpStatus.OK); expect(updatedUser.body.details).toBe('User updated successfully'); }); it('Should delete a user', async () => { const deletedUser = await request(app).delete(`/users/${tempUser.id}`).set('Authorization', token); expect(deletedUser.status).toBe(200); expect(deletedUser.body.details).toBe('User deleted successfully'); }); });
修改后的仓库方法
async delete (id) { let t; try { t = await db.sequelize.transaction(); await User.update( { is_active: false }, { where: {id: id}, transaction: t } ); await t.commit(); } catch (error) { // 确保事务存在时再回滚 if (t) await t.rollback(); throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR,'Error while deleting user'); } };
内容的提问来源于stack exchange,提问作者Louis
相关产品推荐
相关产品推荐

