authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
米哈伊尔·安格诺夫
验证专家 在工程
21 Years 的经验

米哈伊尔拥有物理学硕士学位. 他和诺德关系很好.js, Go, JavaScript spa, React.. js, Flux/Redux, RIOT.js和AngularJS.

专业知识

Share

测试是构建健壮Node的重要组成部分.js应用程序. Proper tests can easily overcome a lot of shortcomings that developers may point out about Node.Js开发方案.

虽然许多开发人员关注单元测试的100%覆盖率, it is important that the code you write is not just tested in isolation. Integration and end-to-end tests give you that extra confidence by testing parts of your application together. 这些部件可能自己工作得很好, 但是在一个大系统中, 代码单元很少单独工作.

Node.js和MongoDB together form one of the most popular duos of recent times. 如果你恰好是使用它们的许多人中的一员,那么你很幸运.

在本文中, you will learn how to write integration and end-to-end tests easily for your Node.js和MongoDB application that run on real instances of the database all without needing to set up an elaborate environment or complicated setup/teardown code.

You will see how the mongo-unit package helps with integration and end-to-end testing in Node.js. 以获取更全面的Node概述.Js集成测试,参见 这篇文章.

处理真实的数据库

通常, 用于集成或端到端测试, your scripts will need to connect to a real dedicated database for testing purposes. This involves writing code that runs at the beginning and end of every test case/suite to ensure that the database is in a clean predictable state.

这可能对某些项目很有效,但也有一些局限性:

  • 测试环境可能相当复杂. 您需要在某个地方保持数据库运行. 这通常需要额外的工作来设置CI服务器.
  • 数据库和操作可能相对较慢. Since the database will use network connections and the operations will require file system activity, 快速运行数千个测试可能并不容易.
  • 数据库保存状态,这对于测试来说不是很方便. Tests should be independent of each other, but using a common DB could make one test affect others.

另一方面, using a real database makes the test environment as close to production as possible. 这可以看作是这种方法的一个特殊优势.

使用真实的内存数据库

使用真实的数据库进行测试似乎有一些挑战. 但是,使用真实数据库的优势太好了,无法传递. 我们如何应对挑战并保持优势?

Reusing a good solution from another platform and applying it to the Node.Js的世界可以是这里的路.

Java项目广泛使用DBUnit和内存数据库.g.(H2)用于此目的.

DBUnit is integrated with JUnit (the Java test runner) and lets you define the database state for each test/testing suite, etc. 它消除了上面讨论的约束:

  • DBUnit and H2 are Java libraries, so you do not need to set up an extra environment. 这一切都在JVM中运行.
  • 内存中的数据库使得这种状态管理非常快.
  • DBUnit makes the database configuration very simple and allows you to keep a clear database state for each case.
  • H2是一个SQL数据库,它与MySQL部分兼容, 在重大案件中, 应用程序可以像使用生产数据库一样使用它.

Taking from these concepts, I decided to make something similar for Node.. js和MongoDB: Mongo-unit.

Mongo-unit是一个Node.可以使用NPM或Yarn安装. 它在内存中运行MongoDB. It makes integration tests easy by integrating well with Mocha and providing a simple API to manage the database state.

图书馆使用 mongodb-prebuilt NPM package, which contains prebuilt MongoDB binaries for the popular operating systems. 这些MongoDB实例可以在内存模式下运行.

安装Mongo-unit

要将mongo-unit添加到你的项目中,你可以运行:

npm install -D mongo-unit

or

纱线添加蒙古单位

就是这样. You do not even need MongoDB installed on your computer to use this package.

使用蒙古包单元进行集成测试

假设您有一个简单的Node.Js应用程序来管理任务:

/ /服务.js

Const 猫鼬 = 要求(“猫鼬')
const mongoUrl =进程.env.MONGO_URL || 'mongodb://localhost:27017/example'
猫鼬.连接(mongoUrl)
const TaskSchema = new 猫鼬.模式({
 名称:字符串,
 :开始日期,
 完成:布尔,
})
const任务=猫鼬.模型(“任务”,TaskSchema)

module.出口= {
 getTasks: () => Task.find(),
 addTask: data => new Task(data).save(),
 deleteTask: taskId => Task.findByIdAndRemove (taskId)
}

MongoDB连接URL在这里不是硬编码的. As with most web application back-ends, we are taking it from the environment variable. 这将允许我们在测试期间将其替换为任何URL.

Const express = 要求(“express')
const bodyParser = 要求(“body-parser')
Const service = 要求(“./服务”)
Const app = express()
app.使用(bodyParser.json())

app.使用(表达.静态的(“$ {__dirname} /静态”))
app.get('/example', (req, res) => {
 service.getTasks ().then(tasks => res.json(任务))
})
app.post('/example', (req, res) => {
 service.addTask(要求.body).then(data => res.json(数据))
})
app.delete('/example/:taskId', (req, res) => {
 service.deleteTask(要求.params.taskId).then(data => res.json(数据))
})
app.listen(3000, () => console.日志('在端口3000启动'))

这是一个具有用户界面的示例应用程序的代码片段. 为简洁起见,省略了UI的代码. 您可以查看完整的示例 GitHub上.

与摩卡集成

让Mocha运行针对mongo-unit的集成测试, we need to run the mongo-unit database instance before the application code is loaded in the Node.js上下文. 要做到这一点,我们可以使用 摩卡,需要 参数和Mocha-prepare库, which allows you to perform asynchronous operations in the require scripts.

/ / it-helper.js
Const prepare = 要求(“mocha-prepare')
const monounit = 要求(“mongo-unit')

prepare(done => mongoUnit.start()
 .then(testMongoUrl => {
   process.env.MONGO_URL = testmongodb
   done()
 }))

编写集成测试

第一步是将测试添加到测试数据库(testData.json):

{
   “任务”:[
   {
     “名称”:“测试”,
     “开始”:“2017 - 08 - 28 - t16:07:38.268Z",
     “完成”:假的
   }
 ]
}

下一步是添加测试本身:

Const expect = 要求(“chai').expect
Const 猫鼬 = 要求(“猫鼬')
const monounit = 要求(“../指数”)
Const service = 要求(“./应用程序/服务”)
const testMongoUrl =进程.env.MONGO_URL

describe('service', () => {
 const testData = 要求(“./夹具/ testData.json')
 beforeEach(() => mongoUnit.initDb (testMongoUrl testData))
 afterEach(() => mongoUnit.drop())

 it('should find all tasks', () => {
   返回服务.getTasks ()
     .then(tasks => {
       期望(任务.length).to.= (1)
       期望(任务[0].name).to.平等(测试)
     })
 })

 it('should create new task', () => {
   返回服务.addTask({name: 'next', completed: false})
     .then(task => {
       期望(任务.name).to.平等(下)
       期望(任务.完成).to.平等的(错误的)
     })
     .then(() => service.getTasks ())
     .then(tasks => {
       期望(任务.length).to.等于(2)
       期望(任务[1].name).to.平等(下)
     })
 })

 it('should remove task', () => {
   返回服务.getTasks ()
     .then(tasks => tasks[0]._id)
     .then(taskId => service.deleteTask (taskId))
     .then(() => service.getTasks ())
     .then(tasks => {
       期望(任务.length).to.equal(0)
     })
 })
})

而且,瞧!

Notice how there are just a couple of lines of code dealing with setup and teardown.

As you can see, it’s very easy to write integration tests using the mongo-unit library. We do not mock MongoDB itself, and we can use the same Mongoose models. We have full control of the database data and do not lose much on test performances since the fake MongoDB在内存中运行.

This also allows us to apply the best unit testing practices for integration tests:

  • 使每个测试独立于其他测试. We load fresh data before each test, giving us a totally independent state for each test.
  • 对每个测试使用最小要求状态. 我们不需要填充整个数据库. We only need to set the minimum required data for each particular test.
  • 我们可以为数据库重用一个连接. 它提高了测试性能.

作为奖励,我们甚至可以在mongo-unit上运行应用程序本身. It allows us to make end-to-end tests for our application against a mocked database.

端到端测试

对于端到端测试,我们将使用 硒WebDriver and 赫敏E2E测试员.

首先,我们将引导驱动程序和测试运行程序:

const monounit = 要求(“mongo-unit')
Const selenium = 要求(“selenium-standalone')
const Hermione = 要求(“ Hermione ')
新赫敏()./ e2e赫敏.conf.Js’)//赫敏配置

seleniumInstall() //确保安装了selenium
 .然后(seleniumStart) //启动selenium web驱动程序
 .然后(mongoUnit.Start) //启动mongo单元
 .then(testMongoUrl => {
   process.env.MONGO_URL = testmongodb //存储mongo的url
 })
 .then(() => {
   要求(“./index.Js’)//启动应用程序
 })
 .然后(delay(1000)) //等待一秒钟,直到应用程序启动
 .then(() => hermione.run(", heroneopts)) //运行heronee2e测试
 .then(() => process.退出(0))
 .catch(() => process.退出(1))

We will also need some helper functions (error handling removed for brevity):

函数seleniumInstall() {
 return new Promise(resolve => selenium.解决安装({}))
}

函数seleniumStart() {
 return new Promise(resolve => selenium.开始(解决)
}

函数delay(timeout) {
 return new Promise(resolve => setTimeout(resolve, timeout))
}

After filling the database with some data and cleaning it once the tests are done, 我们可以运行第一个测试:

Const expect = 要求(“chai').expect
Const co = 要求(“co')
const monounit = 要求(“../指数”)
const testMongoUrl =进程.env.MONGO_URL
const DATA = 要求(“./夹具/ testData.json')

Const UI = {
 task: '.task',
 删除:“.task .remove',
 名称:“#名称”,
 日期:“#日期”,
 addTask:“# addTask”
}

describe('Tasks', () => {

 beforeEach(function () {
   返回mongoUnit.initDb (testMongoUrl、数据)
     .then(() => this.browser.url (http://localhost: 3000))
 })

 afterEach(() => mongoUnit.dropDb (testMongoUrl))

 它('应该显示任务列表',function () {
   Const browser = this.browser
   返回co(function* () {
     Const任务= yield浏览器.(ui元素.task)
     期望(任务.长度,1)
   })
 })

 It ('should create task', function () {
   Const browser = this.browser
   返回co(function* () {
     收益率的浏览器.ui元素(.name).setValue(测试)
     收益率的浏览器.ui元素(.addTask).click()
     Const任务= yield浏览器.(ui元素.task)
     期望(任务.长度,2)
   })
 })

 It ('should remove task', function () {
   Const browser = this.browser
   返回co(function* () {
     收益率的浏览器.ui元素(.remove).click()
     Const任务= yield浏览器.(ui元素.task)
     期望(任务.长度,0)
   })
 })
})

As you can see, the end-to-end tests look very similar to the integration tests.

Wrap Up

Integration and end-to-end testing are important for any large-scale application. Node.js应用程序s, in particular, can benefit tremendously from automated testing. 与mongo-unit, you can write integration and end-to-end testing without worrying about all the challenges that come with such tests.

你可以找到 完整的例子 如何在GitHub上使用mongo-unit.

了解基本知识

  • 什么是集成测试?

    An integration test is an automated test that is used to verify if multiple components of a system work correctly for various cases.

  • E2E代表什么?

    E2E is short for end-to-end, and is generally used in the context of end-to-end testing.

聘请Toptal这方面的专家.
现在雇佣
米哈伊尔·安格诺夫

米哈伊尔·安格诺夫

验证专家 在工程
21 Years 的经验

下诺夫哥罗德,下诺夫哥罗德州,俄罗斯

2015年7月6日加入

作者简介

米哈伊尔拥有物理学硕士学位. 他和诺德关系很好.js, Go, JavaScript spa, React.. js, Flux/Redux, RIOT.js和AngularJS.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

专业知识

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

Toptal开发者

加入总冠军® 社区.