播种数据库
当你为后端编写测试时,你需要测试四种不同类型的操作:
- 创建(用于向数据库添加内容)
- 读取(从数据库中获取数据)
- 更新(用于更改数据库)
- 删除(从数据库中删除内容)
最容易测试的类型是创建操作。你将某些内容放入数据库,然后测试它是否存在。
对于其他三种类型的操作,您需要在编写测试之前将一些内容放入数据库中。
将数据放入数据库
向数据库添加初始内容的过程称为播种。
假设您想向数据库添加三个用户。这些用户包含一个姓名和一个电子邮件地址。
const users = [
{
name: "Zell",
email: "testing1@gmail.com"
},
{
name: "Vincy",
email: "testing2@gmail.com"
},
{
name: "Shion",
email: "testing3@gmail.com"
}
];
您可以在测试开始时使用模型来播种数据库。
const User = require("../model/User"); // Link to User model
it("does something", async done => {
// Add users to the database
for (const u of users) {
const user = new User(u);
await user.save();
}
// Create the rest of your test here
});
如果每次测试都需要这些用户,最好的方法是通过beforeEach
钩子添加它们。beforeEach
钩子在每个it
声明之前运行。
// Seed the database with users
beforeEach(async () => {
for (u of users) {
const user = new User(u);
await user.save();
}
});
你也可以使用 Mongoose 的create
函数来实现同样的效果。它运行new Model()
和save()
,所以下面的代码和上面的代码做的是相同的。
// Seed the database with users
beforeEach(async () => {
await User.create(users);
});
创建 vs 插入多个
Mongoose 有第二种方法可以帮助你为数据库设置种子。这种方法称为insertMany
。insertMany
它比 更快create
,因为:
insertMany
向服务器发送一个操作create
为每个文档发送一个操作
但是,insertMany
不运行save
中间件。
触发保存中间件重要吗?
这取决于你的种子数据。如果你的种子数据需要经过save
中间件,则需要使用create
。例如,假设你想将用户密码保存到数据库中。你有以下数据:
const users = [
{
name: "Zell",
email: "testing1@gmail.com",
password: "12345678"
},
{
name: "Vincy",
email: "testing2@gmail.com",
password: "12345678"
},
{
name: "Shion",
email: "testing3@gmail.com",
password: "12345678"
}
];
当我们将用户密码保存到数据库时,出于安全考虑,我们需要对密码进行哈希处理。我们通常通过save
中间件对密码进行哈希处理。
// Hashes password automatically
userSchema.pre("save", async function(next) {
if (!this.isModified("password")) return next();
const salt = bcrypt.genSaltSync(10);
const hashedPassword = bcrypt.hashSync(password, salt);
this.password = hashedPassword;
});
如果您使用create
,您将获得具有散列密码的用户:
如果您使用insertMany
,您将获得没有散列密码的用户:
何时使用 create,何时使用 insertMany
由于insertMany
比更快,因此您希望尽可能create
使用。insertMany
以下是我的做法:
- 如果种子数据不需要
save
中间件,则使用insertMany
。 - 如果种子数据需要
save
中间件,请使用create
。然后覆盖种子数据,使其不再需要save
中间件。
对于上面的密码示例,我会create
先运行。然后,我复制粘贴散列后的密码种子数据。然后,我会insertMany
从此处继续运行。
如果要覆盖复杂的种子数据,可能需要直接从 MongoDB 获取 JSON。为此,您可以使用mongoexport
:
mongoexport --db <databaseName> --collection <collectionName> --jsonArray --pretty --out output.json
这说:
- 导出
<collection>
自<databaseName>
- 将输出创建为 JSON 数组,并进行美化,保存在名为 的文件中
output.json
。该文件将放置在运行命令的文件夹中。
播种多个测试文件和集合
您需要一个地方来存储种子数据,以便在所有测试和集合中使用它们。这是我使用的系统:
- 我根据种子文件的模型来命名它们。我
User
用这个user.seed.js
文件来为一个模型播种。 - 我把种子文件放在
seeds
文件夹中 - 我循环遍历每个种子文件来为数据库播种。
要循环遍历每个种子文件,您需要使用fs
模块。fs
代表文件系统。
循环遍历文件最简单的方法是index.js
在同一个seeds
文件夹中创建一个文件。创建好index.js
文件后,你可以使用以下代码查找所有文件*.seed.js
const fs = require("fs");
const util = require("util");
// fs.readdir is written with callbacks.
// This line converts fs.readdir into a promise
const readDir = util.promisify(fs.readdir);
async function seedDatabase() {
// Gets list of files in the directory
// `__dirname` points to the `seeds/` folder
const dir = await readDir(__dirname);
// Gets a list of files that matches *.seed.js
const seedFiles = dir.filter(f => f.endsWith(".seed.js"));
}
有了种子文件列表后,就可以循环遍历每个种子文件来填充数据库。这里,我使用for...of
循环来简化操作。
async function seedDatabase() {
for (const file of seedFiles) {
// Seed the database
}
}
要为数据库添加种子,我们需要根据种子文件的名称找到正确的 Mongoose 模型。名为 的文件user.seed.js
应该可以作为模型的种子User
。这意味着:
- 我们必须
user
找到user.seed.js
- 我们必须
user
利用User
这是一个粗略的版本,可以完成所需的功能。(如果您愿意,可以使用正则表达式而不是 ,使代码更健壮split
)。
for (const file of seedFiles) {
const fileName = file.split(".seed.js")[0];
const modelName = toTitleCase(fileName);
const model = mongoose.models[modelName];
}
接下来,我们要确保每个文件都有一个与之对应的模型。如果找不到该模型,我们会抛出一个错误。
for (const file of seedFiles) {
//...
if (!model) throw new Error(`Cannot find Model '${modelName}'`);
}
如果有对应的模型,我们需要用种子文件中的内容填充数据库。为此,我们需要先读取种子文件。由于我使用了.js
扩展,因此在这里我可以直接 require 该文件。
for (const file of seedFiles) {
//...
const fileContents = require(path.join(__dirname, file));
}
为了使其工作,我的种子文件必须导出一个数据数组。
module.exports = [
{
name: "Zell",
email: "testing1@gmail.com",
password: "12345678"
},
{
name: "Vincy",
email: "testing2@gmail.com",
password: "12345678"
},
{
name: "Shion",
email: "testing3@gmail.com",
password: "12345678"
}
];
一旦我有了种子文件的内容,我就可以运行create
或insertMany
。
async function seedDatabase(runSaveMiddleware = false) {
// ...
for (const file of seedFiles) {
// ...
runSaveMiddleware
? model.create(fileContents)
: model.insertMany(fileContents);
}
}
以下是整个seedDatabase
代码:
const fs = require("fs");
const util = require("util");
const readDir = util.promisify(fs.readdir).bind(fs);
const path = require("path");
const mongoose = require("mongoose");
function toTitleCase(str) {
return str.replace(/\w\S*/g, txt => {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
}
async function seedDatabase(runSaveMiddleware = false) {
const dir = await readDir(__dirname);
const seedFiles = dir.filter(f => f.endsWith(".seed.js"));
for (const file of seedFiles) {
const fileName = file.split(".seed.js")[0];
const modelName = toTitleCase(fileName);
const model = mongoose.models[modelName];
if (!model) throw new Error(`Cannot find Model '${modelName}'`);
const fileContents = require(path.join(__dirname, file));
runSaveMiddleware
? await model.create(fileContents)
: await model.insertMany(fileContents);
}
}
为什么是 JS,而不是 JSON?
使用 JSON 存储数据是行业规范。在这种情况下,我发现使用 JavaScript 对象更方便,因为:
- 我不需要为每个属性写出开始和结束双引号。
- 我根本就不需要用双引号!(用单引号更容易,因为不需要按 Shift 键)。
// Which is easier to write. JavaScript objects or JSON?
// JavaScript objects
module.exports = [
{
objectName: "property"
}
][
// JSON
{
objectName: "property"
}
];
如果您想使用 JSON,请确保将其更改seedDatabase
为使用 JSON。(我会让您自己完成代码)。
调整setupDB函数
在上一篇文章中,我创建了一个setupDB
函数来帮助我为测试设置数据库。seedDatabase
进入该setupDB
函数,因为播种是设置过程的一部分。
async function seedDatabase(runSaveMiddleware = false) {
// ...
}
module.exports = {
setupDB(databaseName, runSaveMiddleware = false) {
// Connect to Mongoose
beforeAll(/*...*/);
// Seed Data
beforeEach(async () => {
await seedDatabase(runSaveMiddleware);
});
// Cleans up database between each test
afterEach(/*...*/);
// Disconnect Mongoose
afterAll(/*...*/);
}
};
Github 仓库
我创建了一个Github 仓库来配合这三部分的测试系列。希望这些演示代码能帮助你开始测试你的应用程序。
感谢阅读。本文最初发布在我的博客上。如果您想阅读更多文章来帮助您成为更优秀的前端开发人员,请订阅我的新闻通讯。
鏂囩珷鏉ユ簮锛�https://dev.to/zellwk/seeding-a-database-1847