Тестирование Mongoose-моделей с MongoDB Memory Server и Jest
Используйте mongodb-memory-server, чтобы запускать реальную MongoDB в оперативной памяти для тестов Mongoose. Это даёт быстрые, изолированные интеграционные тесты без риска засорить реальную БД. В руководстве показано, как создать модель, настроить память, написать и прогонять тесты в Jest, а также даны советы по CI и отладке.

Что такое MongoDB Memory Server?
MongoDB Memory Server — это инструмент, который запускает полноценный процесс MongoDB, но хранит данные в памяти. Коротко:
- Назначение: изолировать тестовую базу от реальной. По сути — «реальная» база без дисковых операций.
- Польза: быстрее, безопаснее, удобно для CI/CD.
- Ограничения: не подходит для тестирования сценариев, требующих специфического поведения хранения на диске, многоузловых кластеров, сложных операций с репликацией или сторедж-энджин-специфичных фич.
Определение терминов:
- Mongoose — ODM (Object Data Modeling) для Node.js и MongoDB.
- Jest — тестовый раннер и фреймворк утверждений от Facebook.
Когда это удобно и когда не годится
Важно: MongoDB Memory Server отлично подходит для интеграционных тестов моделей и запросов. Он плохо подходит, если вы тестируете:
- ведение репликации и переключение мастера;
- поведение на уровне файловой системы и драйвера (специфичные сторедж-энджины);
- нагрузочное тестирование, где важен I/O на диск.
Создаём модель Mongoose
Мы будем работать с простой моделью «todo» с полями item и completed.
- Инициализируйте проект:
mkdir mongoose-model-test
cd mongoose-model-test
npm init -y- Установите Mongoose:
npm install mongoose- Создайте файл todo.model.js и определите схему. Ниже — корректная и рабочая версия:
const mongoose = require("mongoose");
const { Schema } = mongoose;
const TodoSchema = new Schema({
item: {
type: String,
required: true
},
completed: {
type: Boolean,
required: true
}
});
module.exports = mongoose.model('Todo', TodoSchema);Пояснение: поле completed было в исходном тексте с опечаткой. Здесь оно исправлено — это критично для корректной валидации.
План тестов
Минимальный набор проверок для модели:
- Успешная запись валидного документа.
- Ошибка при отсутствии обязательного поля.
- Ошибка при неверном типе данных для поля.
Эти тесты покрывают базовую валидацию схемы.
Настройка MongoDB Memory Server для тестов
Установите пакет:
npm install mongodb-memory-server --save-devСоздайте файл setupTestDB.js (имя важно — используйте единообразно при импорте) и добавьте логику запуска/остановки инстанса и очистки коллекций. Корректный пример:
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
let mongo = null;
const connectDB = async () => {
mongo = await MongoMemoryServer.create();
const uri = mongo.getUri();
await mongoose.connect(uri, {
// опции Mongoose 7+ могут не требовать явных флагов, но указаны здесь для совместимости
useNewUrlParser: true,
useUnifiedTopology: true
});
};
const dropDB = async () => {
if (mongo) {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
await mongo.stop();
}
};
const dropCollections = async () => {
if (mongo) {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
// .deleteMany({}) безопаснее, чем deprecated .remove() в современных версиях
await collection.deleteMany({});
}
}
};
module.exports = { connectDB, dropDB, dropCollections };Важно: в исходном руководстве использовались устаревшие методы и несогласованность имён файлов. В примере выше мы используем deleteMany({}) вместо remove() и единое имя файла setupTestDB.js.
Конфигурация Jest
Установите Jest:
npm install --save-dev jestВ package.json замените scripts и добавьте конфигурацию jest:
"scripts": {
"test": "jest --runInBand --detectOpenHandles"
},
"jest": {
"testEnvironment": "node"
},Пояснение параметров:
- –runInBand — запуск тестов в одном процессе, полезно при использовании in-memory-базы.
- –detectOpenHandles — помогает выявить открытые подключения, если тесты не завершаются.
Пишем тесты (todo.model.test.js)
Создайте файл todo.model.test.js и импортируйте зависимости:
const mongoose = require('mongoose');
const { connectDB, dropDB, dropCollections } = require('./setupTestDB');
const Todo = require('./todo.model');Добавьте хуки Jest для подключения и очистки:
beforeAll(async () => {
await connectDB();
});
afterAll(async () => {
await dropDB();
});
afterEach(async () => {
await dropCollections();
});Теперь сами тесты. Ниже — три теста: успешное создание, отсутствие required-поля и неверный тип.
describe('Todo Model', () => {
it('should create a todo item successfully', async () => {
let validTodo = {
item: 'Do the dishes',
completed: false
};
const newTodo = new Todo(validTodo);
await newTodo.save();
expect(newTodo._id).toBeDefined();
expect(newTodo.item).toBe(validTodo.item);
expect(newTodo.completed).toBe(validTodo.completed);
});
it('should fail for todo item without required fields', async () => {
let invalidTodo = {
item: 'Do the dishes'
// отсутствует completed
};
try {
const newTodo = new Todo(invalidTodo);
await newTodo.save();
} catch (error) {
expect(error).toBeInstanceOf(mongoose.Error.ValidationError);
expect(error.errors.completed).toBeDefined();
}
});
it('should fail for todo item with fields of wrong type', async () => {
let invalidTodo = {
item: 'Do the dishes',
completed: 'False' // строка вместо булева
};
try {
const newTodo = new Todo(invalidTodo);
await newTodo.save();
} catch (error) {
expect(error).toBeInstanceOf(mongoose.Error.ValidationError);
expect(error.errors.completed).toBeDefined();
}
});
});Пояснение: мы используем try/catch, чтобы поймать ValidationError и проверить, что ошибка связана с ожидаемым полем.
Приёмочные критерии
- Валидный документ сохраняется и получает _id.
- При отсутствии обязательного поля сохраняться не должен и выбрасывается mongoose.ValidationError.
- При неверном типе поля сохраняться не должен и выбрасывается mongoose.ValidationError.
Отладка и часто встречаемые проблемы
- Тесты зависают после выполнения. Частая причина — не закрытое подключение к MongoDB. Убедитесь, что послеAll вызывается dropDB и mongo.stop(). Используйте –detectOpenHandles для диагностики.
- Несоответствие имён файлов при импорте. Держите имена файлов и экспорты в синхронизации (например, setupTestDB.js и ./setupTestDB).
- Различия версий Mongoose. Опции подключения (useNewUrlParser и useUnifiedTopology) могут быть уже не обязательны в новых версиях Mongoose; проверьте документацию вашей версии.
Important: в CI иногда МongoMemoryServer скачивает бинарники при первом запуске. Кэшируйте бинарники в CI, чтобы ускорить билды (например, в GitHub Actions сохраните ~/.cache/mongodb-binaries).
Альтернативные подходы
- Mocking Mongoose: быстрый, но не покрывает реальную работу драйвера и схемы.
- Отдельный локальный MongoDB контейнер (docker-compose): ближе к проду, но медленнее и сложнее в CI.
- Testcontainers (контейнеры Docker для тестов): гарантирует среду, близкую к продакшен, но требует Docker в CI.
Ментальная модель выбора:
- Unit-tests: мокайте отдельные функции (без реальной БД).
- Integration-tests: mongodb-memory-server — лёгкий и быстрый выбор.
- E2E: контейнеры/реальная инфраструктура.
Чек-лист по ролям
Разработчик:
- Создал корректную схему Mongoose.
- Написал тесты на позитивные и негативные сценарии.
- Убедился в закрытии подключений.
Код-ревьювер:
- Проверил корректность типов и required-полей.
- Проверил, что в тестах нет обращения к реальной базе.
CI-инженер:
- Настроил кэш для mongodb-binaries.
- Убедился, что runner поддерживает необходимые флаги Docker (если используются контейнеры).
Snippets / Cheatsheet
- Запустить только один тест-файл:
npm test -- todo.model.test.js- Быстро запустить Jest в watch-режиме (локально):
npx jest --watch- Очистка коллекций (внутри setup файла):
for (let collection of collections) {
await collection.deleteMany({});
}Совместимость и миграции
- Если вы обновляете Mongoose, проверьте изменения API подключения и поведения схем.
- MongoMemoryServer загружает бинарники MongoDB. Для воспроизводимости тестов фиксируйте версию пакета в package.json.
Безопасность и конфиденциальность
Тестовые данные в памяти не попадают в реальную базу. Тем не менее:
- Не храните реальные персональные данные в тестовых наборах.
- Если вы подаёте фикстуры, убедитесь в их анонимности.
Примеры, когда такой подход не подойдёт
- Тестирование транзакций в шардированной/реплицированной топологии.
- Низкоуровневые тесты драйвера, зависящие от конкретной версии mongod и его опций.
Краткое резюме
MongoDB Memory Server — простой и мощный инструмент для тестирования Mongoose-моделей. Он даёт изолированную, быструю и безопасную среду для интеграционных тестов. Используйте его для проверки валидации схем, CRUD-операций и бизнес-логики, но выбирайте другие подходы для тестов, чувствительных к реальному поведению дисковой подсистемы или кластеризации.
Notes: поддерживайте единообразие имён файлов и корректные экспорты. В CI кэшируйте бинарники MongoDB, чтобы избежать длительных загрузок при каждом запуске.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone