发布于 2026-01-06 1 阅读
0

使用 MongoDB 内存服务器进行测试

使用 MongoDB 内存服务器进行测试

我最近一直在深入研究测试。我花时间创建代码可靠性较高的项目。最终目标是创建并部署经过充分测试且代码覆盖率良好的应用程序。

我发现,测试数据库并非总是那么简单。以下是我在一个项目中进行数据库测试的概述。

背景故事🔙

我目前正在开发的项目名为OnLearn,它本质上是一个在线学习管理系统的概念验证项目。该系统将作为一个平台,供潜在用户发布课程或学习课程。实际上,它与 Udemy、SkillShare 或任何其他 MOOC 平台都非常相似。

该应用的技术栈包括 Node.js 和 MongoDB(Mongoose ODM),视图框架使用 Handlebars。测试框架则采用 Jest。

问题🤔

遇到的首要挑战之一是MongoDB的测试。我希望能够为数据库逻辑编写单元测试,而无需大量依赖模拟对象。

在研究了各种解决方案之后,我找到了两篇关于使用内存数据库测试 MongoDB 的文章:

☝️用于测试的内存 MongoDB。✌️
Paula SantamaríaNode.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
Enter fullscreen mode Exit fullscreen mode

2️⃣ 配置 Jest。

👉测试脚本使用以下命令
添加脚本。testpackage.json

"scripts": {
    "test": "jest --runInBand --detectOpenHandles",
}
Enter fullscreen mode Exit fullscreen mode

CLI 选项概述

  • "test"-指的是运行测试的脚本名称。
  • jest-运行所有测试的默认命令。
  • --runInBand-该命令会在当前进程中按顺序运行所有测试,而不是创建一个运行测试的子进程工作池。
  • --detectOpenHandles-该命令将尝试收集并打印阻止 Jest 正常退出的打开句柄。

👉测试环境
Jest 的默认环境是类似浏览器的环境。jsdom.对于 Node 应用程序,应该指定类似 Node 的环境。

"jest": {
    "testEnvironment": "node",
}
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

仔细看看发生了什么:

进口mongoosemongodb-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);

Enter fullscreen mode Exit fullscreen mode

5️⃣ 编写单元测试。

mongo-memory-server最后,使用创建的操作来建立与单元测试的连接。

以下是应用程序中用户模型测试的示例。测试用例和断言分别放置在不同的模块中……

👉赛程安排

// database/fixtures/index.js

exports.fakeUserData = {
  firstName: 'Dummy',
  lastName: 'User',
  username: 'dummyUser',
  email: 'dummy@user.com',
  password: '********',
  role: 'student',
};
Enter fullscreen mode Exit fullscreen mode

👉测试断言助手

// 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);
};
Enter fullscreen mode Exit fullscreen mode

最后,测试中使用了测试夹具、断言辅助函数和数据库操作。🥳🥳🥳

👉用户模型单元测试

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);
    }
  });
});

Enter fullscreen mode Exit fullscreen mode

通过测试
通过测试截图

您可以在这里找到所有测试和实现。

结论🏁

最终,这个mongodb-memory-server软件包为我的测试做了很多繁重的数据库工作。我使用 ` dbConnectand`dbDisconnect操作来测试我的应用程序模型,甚至还测试了与这些模型关联的服务

你觉得怎么样?
也欢迎分享任何改进建议。✌️


找到 mongodb-memory-server 代码库 👉点击这里👈
找到 OnLearn 代码库 👉点击这里👈

文章来源:https://dev.to/remrkabledev/testing-with-mongodb-memory-server-4ja2