使用 MongoDB 内存服务器进行测试
我最近一直在深入研究测试。我花时间创建代码可靠性较高的项目。最终目标是创建并部署经过充分测试且代码覆盖率良好的应用程序。
我发现,测试数据库并非总是那么简单。以下是我在一个项目中进行数据库测试的概述。
背景故事🔙
我目前正在开发的项目名为OnLearn,它本质上是一个在线学习管理系统的概念验证项目。该系统将作为一个平台,供潜在用户发布课程或学习课程。实际上,它与 Udemy、SkillShare 或任何其他 MOOC 平台都非常相似。
该应用的技术栈包括 Node.js 和 MongoDB(Mongoose ODM),视图框架使用 Handlebars。测试框架则采用 Jest。
问题🤔
遇到的首要挑战之一是MongoDB的测试。我希望能够为数据库逻辑编写单元测试,而无需大量依赖模拟对象。
在研究了各种解决方案之后,我找到了两篇关于使用内存数据库测试 MongoDB 的文章:
☝️用于测试的内存 MongoDB。✌️
Paula Santamaría的Node.js + Mongoose 测试指南
两篇文章中,作者都提到了nodkz 的 mongodb-memory-server软件包。
什么是 mongodb-memory-server?
它是一个可以启动真实 MongoDB 服务器的软件包。它使我们能够启动一个 mongod 进程,该进程会将数据存储在内存中。
内存数据库在应用程序的主内存中启动、运行和关闭。由于它们从不访问硬盘,因此速度很快,而且由于它们在关闭时会立即销毁,因此非常适合测试。
解决方案💡
以下是 mongodb-memory-server 如何帮助我为 OnLearn 应用程序的某个模型编写单元测试:
1️⃣安装依赖项。2️⃣
配置Jest。3️⃣
设置内存数据库。4️⃣创建模型。5️⃣
编写 单元测试。
1️⃣ 安装依赖项。
以下命令将jest同时安装mongodb-memory-server。
npm i jest mongodb-memory-server
2️⃣ 配置 Jest。
👉测试脚本使用以下命令
添加脚本。testpackage.json
"scripts": {
"test": "jest --runInBand --detectOpenHandles",
}
CLI 选项概述
"test"-指的是运行测试的脚本名称。jest-运行所有测试的默认命令。--runInBand-该命令会在当前进程中按顺序运行所有测试,而不是创建一个运行测试的子进程工作池。--detectOpenHandles-该命令将尝试收集并打印阻止 Jest 正常退出的打开句柄。
👉测试环境
Jest 的默认环境是类似浏览器的环境。jsdom.对于 Node 应用程序,应该指定类似 Node 的环境。
"jest": {
"testEnvironment": "node",
}
3️⃣ 设置内存数据库。
另一个单独的文件设置了mongodb-memory-server连接和断开连接的函数。
// utils/test-utils/dbHandler.utils.js
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongoServer = new MongoMemoryServer();
exports.dbConnect = async () => {
const uri = await mongoServer.getUri();
const mongooseOpts = {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
useFindAndModify: false,
};
await mongoose.connect(uri, mongooseOpts);
};
exports.dbDisconnect = async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
await mongoServer.stop();
};
仔细看看发生了什么:
进口
mongoose和mongodb-memory-server.const mongoose = require('mongoose'); const { MongoMemoryServer } = require('mongodb-memory-> server');
mongodb-memory-server用于对内存数据库执行操作的新实例。const mongoServer = new MongoMemoryServer();
用于连接内存数据库的函数。
exports.dbConnect = async () => { const uri = await mongoServer.getUri(); const mongooseOpts = { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: false, }; await mongoose.connect(uri, mongooseOpts); };
用于断开内存数据库连接的函数。
exports.dbDisconnect = async () => { await mongoose.connection.dropDatabase(); await mongoose.connection.close(); await mongoServer.stop(); };
4️⃣ 创建模型。
这是应用程序中的用户模型。
用户通过 Passport本地验证和Google 验证策略进行验证。
因此,用户架构包含以下内容:
local以及google用于身份验证数据的字段。profilePictureUrl用于用户的头像。role针对特定类型的用户。
// database/models/user.model.js
const { Schema, model } = require('mongoose');
const userSchema = new Schema({
local: {
firstName: {
type: String,
trim: true,
},
lastName: {
type: String,
trim: true,
},
username: {
type: String,
trim: true,
unique: true,
},
email: {
type: String,
match: [/^\S+@\S+\.\S+$/, 'Please use a valid email address.'],
unique: true,
lowercase: true,
trim: true,
},
password: { type: String },
},
google: {
id: String,
token: String,
email: String,
name: String,
},
profilePictureUrl: {
type: 'String',
default: 'https://via.placeholder.com/150',
},
role: {
type: String,
enum: ['student', 'instructor', 'admin'],
default: 'student',
},
});
module.exports = model('User', userSchema);
5️⃣ 编写单元测试。
mongo-memory-server最后,使用创建的操作来建立与单元测试的连接。
以下是应用程序中用户模型测试的示例。测试用例和断言分别放置在不同的模块中……
👉赛程安排
// database/fixtures/index.js
exports.fakeUserData = {
firstName: 'Dummy',
lastName: 'User',
username: 'dummyUser',
email: 'dummy@user.com',
password: '********',
role: 'student',
};
👉测试断言助手
// utils/test-utils/validators.utils.js
exports.validateNotEmpty = (received) => {
expect(received).not.toBeNull();
expect(received).not.toBeUndefined();
expect(received).toBeTruthy();
};
...
exports.validateStringEquality = (received, expected) => {
expect(received).not.toEqual('dummydfasfsdfsdfasdsd');
expect(received).toEqual(expected);
};
...
exports.validateMongoDuplicationError = (name, code) => {
expect(name).not.toEqual(/dummy/i);
expect(name).toEqual('MongoError');
expect(code).not.toBe(255);
expect(code).toBe(11000);
};
最后,测试中使用了测试夹具、断言辅助函数和数据库操作。🥳🥳🥳
👉用户模型单元测试
const User = require('../user.model');
const { fakeUserData } = require('../../fixtures');
const {
validateNotEmpty,
validateStringEquality,
validateMongoDuplicationError,
} = require('../../../utils/test-utils/validators.utils');
const {
dbConnect,
dbDisconnect,
} = require('../../../utils/test-utils/dbHandler.utils');
beforeAll(async () => dbConnect());
afterAll(async () => dbDisconnect());
describe('User Model Test Suite', () => {
test('should validate saving a new student user successfully', async () => {
const validStudentUser = new User({
local: fakeUserData,
role: fakeUserData.role,
});
const savedStudentUser = await validStudentUser.save();
validateNotEmpty(savedStudentUser);
validateStringEquality(savedStudentUser.role, fakeUserData.role);
validateStringEquality(savedStudentUser.local.email, fakeUserData.email);
validateStringEquality(
savedStudentUser.local.username,
fakeUserData.username
);
validateStringEquality(
savedStudentUser.local.password,
fakeUserData.password
);
validateStringEquality(
savedStudentUser.local.firstName,
fakeUserData.firstName
);
validateStringEquality(
savedStudentUser.local.lastName,
fakeUserData.lastName
);
});
test('should validate MongoError duplicate error with code 11000', async () => {
expect.assertions(4);
const validStudentUser = new User({
local: fakeUserData,
role: fakeUserData.role,
});
try {
await validStudentUser.save();
} catch (error) {
const { name, code } = error;
validateMongoDuplicationError(name, code);
}
});
});
您可以在这里找到所有测试和实现。
结论🏁
最终,这个mongodb-memory-server软件包为我的测试做了很多繁重的数据库工作。我使用 ` dbConnectand`dbDisconnect操作来测试我的应用程序模型,甚至还测试了与这些模型关联的服务。
你觉得怎么样?
也欢迎分享任何改进建议。✌️
找到 mongodb-memory-server 代码库 👉点击这里👈
找到 OnLearn 代码库 👉点击这里👈
