Заполнение базы данных | Кодементор
Когда вы пишете тесты для серверной части, вам нужно тестировать четыре разных типа операций:
- Создать (для добавления вещей в базу данных)
- Чтение (для получения данных из базы данных)
- Обновление (для изменения базы данных)
- Удалить (для удаления вещей из базы данных)
Самый простой тип для проверки — создание операций. Вы помещаете что-то в базу данных и проверяете, есть ли оно там.
Для остальных трех типов операций вам нужно что-то поместить в базу данных до вы пишете тест.
Внесение вещей в базу данных
Процесс, в котором вы добавляете начальный контент в базу данных, называется засев.
Допустим, вы хотите добавить в базу данных трех пользователей. Эти пользователи содержат имя и адрес электронной почты.
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’s create
функция, чтобы сделать то же самое. Он работает new Model()
а также save()
поэтому приведенный ниже и приведенный выше код делают одно и то же.
// Seed the database with users
beforeEach(async () => {
await User.create(users)
})
создать против вставить много
У 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
с этого момента и далее.
Если вы хотите перезаписать сложные исходные данные, вы можете получить JSON прямо из MongoDB. Для этого вы можете использовать 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
расширение, я могу просто потребовать файл.
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
я создал Репозиторий на гитхабе чтобы пойти с этой серией испытаний из трех частей. Я надеюсь, что этот демонстрационный код поможет вам приступить к тестированию ваших приложений.
Спасибо за чтение. Эта статья изначально была размещена на мой блог. Подписаться на моя рассылка если вы хотите больше статей, которые помогут вам стать лучшим разработчиком внешнего интерфейса.