介绍
测试很重要,它们为您的应用程序或api提供了保障。作为初学者,可能会忘记编写涵盖您正在构建的重要部分的测试的需要。然而,当您作为开发人员取得进步时,您会遇到它。
在之前的教程中,您学习了如何使用 node.js 构建 API。如果你还没有经历过,我建议你在继续之前这样做。在本教程中,您将为使用 Node.js 和 Express 构建的 API 编写测试。在本教程结束时,您将了解 Node.js 中的测试工作原理,并且您将能够构建功能性和经过测试的 API。
测试工具
您将使用 Mocha、Expect 和 Supertest。
Mocha是一个javascript测试框架,它使异步测试变得简单而有趣。Mocha 有很多很棒的功能,可以在网站上找到。您将使用其中的一小部分。
Expect是一个断言库,可让您轻松做出更好的断言。你会看到它是如何工作的。Supertest为测试 HTTP 提供了高级抽象。这是必要的,因为您将测试 API。
废话不多说,是时候写一些代码了。
项目设置
我已经为你设置了一个待办事项列表项目。模型、配置文件和 app.js 的一部分已经为您完成。转到 GitHub 并克隆存储库。或者您可以简单地执行以下操作:
git clone https://github.com/izuchukwu1/node-todo-api.git
你的 package.json 应该是这样的。
#package.json { "name": "node-todo-api", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "start": "node server/server.js", "test": "export NODE_ENV=test || SET \"NODE_ENV=test\" && mocha server/**/*.test.js", "test-watch": "nodemon --exec 'npm test'" }, "engines": { "node": "8.0.0" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "body-parser": "^1.17.2", "express": "^4.15.3", "lodash": "^4.17.4", "mongodb": "^2.2.29", "mongoose": "^4.11.1" }, "devDependencies": { "expect": "^1.20.2", "mocha": "^3.4.2", "nodemon": "^1.11.0", "supertest": "^3.0.0" } }
现在运行命令来安装依赖项。
npm install
发布请求测试
对于您的测试,创建一个名为test的新文件夹;该文件夹应该在您的服务器目录中。现在创建一个新文件,您将在其中编写测试。将文件命名为server.test.js。
在此文件中,首先需要您安装的模块。您还需要要求您的服务器文件和模型。
#server/test/server.test.jsconst expect = require('expect') const request = require('supertest') const {ObjectId} = require('mongodb') const {app} = require('./../server') const {Todo} = require('./../models/todo')
您需要有一些将在测试期间使用的待办事项。但是每次运行测试套件时,这些待办事项都会从测试数据库中删除。为了解决这个问题,像这样创建两个测试。
#server/test/server.test.js const todos = [{ _id: new ObjectId(), text: "First test todo" }, { _id: new ObjectId(), text: "Second test todo", completed: true, completedAt: 333 }] beforeEach((done) => { Todo.remove({}).then(() => { return Todo.insertMany(todos) }).then(() => done()) })
before 块清理你的 Todo 数据库,然后插入上面设置的 to-dos。这可确保您的数据库中有稳定数量的条目,因此您的测试不会遇到问题。
完成后,您可以为post请求编写测试 。对于您的POST请求,您将编写两个测试。第一个将请求有效的待办事项并成功。第二个将使用无效的正文发出请求,这不应创建新的待办事项。
这是测试的样子。
#server/test/server.test.js describe('POST /todos', () => { // 1 it('should create a new todo', (done) => { let text = 'Test todo text' // 2 request(app) // 3 .post('/todos') .send({text}) .expect(200) .expect((res) => { expect(res.body.text).toBe(text) }) .end((err, res) => { // 4 if (err) { return done(err) } Todo.find({text}).then((todos) => { // 5 expect(todos.length).toBe(1) expect(todos[0].text).toBe(text) done() }).catch((e) => done(e)) }) }) it('should not create todo with invalid body data', (done) => { // 6 request(app) // 7 .post('/todos') .send({}) .expect(400) .end((err, res) => { if (err) { return done(err) } Todo.find().then((todos) => { // 8 expect(todos.length).toBe(2) done() }).catch((e) => done(e)) }) }) })
使用以下命令运行测试:
npm run test
它应该失败。这是正在发生的事情:
描述测试。
创建一个新的待办事项并将其保存为文本值。
您向 API 的/todos路径发出 POST 请求,将您创建的待办事项作为请求的主体发送。您希望请求的状态代码为200,待办事项的正文等于 的值
text
。如果有任何错误,则返回错误,这将导致请求结束。因此下一个代码块将不会运行。如果没有遇到错误,则流程继续。
向数据库发出请求以查找创建的待办事项。您希望数据库中待办事项的长度为 1,待办事项的文本等于文本的值。
这是使用无效身体数据进行的测试。
向/todos路径发出POST请求。这一次,您发送的请求没有正文。您预计会收到400 个请求。无需检查正文,因为您没有发送任何内容。如果遇到错误,它会被返回并且代码停止运行。
如果没有遇到错误,则向数据库发出请求以检查待办事项的长度。我们预计数据库将只包含 2 个,它们是一开始创建的待办事项。
要通过此测试,请转到您的server.js文件并放入 POST 请求所需的代码。
#server/server.js app.post('/todos', (req, res) => { let todo = new Todo({ text: req.body.text }) todo.save().then((doc) => { res.send(doc) }, (e) => { res.status(400).send(e) }) })
GET 请求测试
这很简单——测试应该返回数据库中可用的待办事项的长度。正如你已经知道的,待办事项的长度应该是 2。为什么?你猜对了。在测试套件开始时,您创建了一个beforeEach
块来清理您的数据库并在每次运行测试套件时插入新的待办事项。
为了测试获取所有待办事项的请求是否有效,这里是它的代码。
#server/test/server.test.js describe('GET /todos', () => { it('should get all todos', (done) => { request(app) .get('/todos') .expect(200) .expect((res) => { expect(res.body.todos.length).toBe(2) }) .end(done) }) })
在上面,您正在向/todos路径发出GET请求 。这一次,您没有将任何内容作为请求的主体传递,因为它是一个GET请求。您希望收到状态码为200的响应。然后您期望待办事项的长度为 2。
当你运行测试时,你应该得到一个错误。尝试让错误自行传递。
我敢打赌你一定能做到。这是使测试通过的代码;将其与您的解决方案进行比较。
#server/server.js app.get('/todos', (req, res) => { Todo.find().then((todos) => { res.send({todos}) }, (e) => { res.status(400).send(e) }) })
当对/todos路径发出GET请求 时,您希望找到 Todo 集合中的每个待办事项并将它们作为待办事项返回。将此添加到server.js会导致测试通过。
接下来,您将为检索单个待办事项而发出的GET请求编写三个测试。第一个应该检索并返回待办事项。在未找到待办事项的情况下,第二个和第三个应该返回404错误。
打开你的server.test.js并使用第一个测试用例创建一个新的 describe 块。
#server/server.test.js describe('GET /todos/:id', () => { it('should return todo doc', (done) => { request(app) .get(`/todos/${todos[0]._id.toHexString()}`) .expect(200) .expect((res) => { expect(res.body.todo.text).toBe(todos[0].text) }) .end(done) })
此测试发出GET请求以检索数据库中可用的第一个待办事项。您期望获得200状态代码,然后检查待办事项的文本值是否与创建的相同。
下一个测试看起来像这样。
it('should return 404 if todo is not found', (done) => { let _id = new ObjectId('5967989ee978311656e93a59') request(app) .get(`/todos/${todos/_id.toHexString()}`) .expect(404) .end(done) })
在这里,您正在使用与保存在数据库中的任何待办事项的 ID 不对应的 ID 查找待办事项。测试期望此请求返回404错误。
最后一个测试和第一个一样,但有一点不同;这是它的外观。
it('should return 404 for non-object ids', (done) => { let hexId = '5967989ee978311656e93a5312' request(app) .get(`/todos/${todos/hexId}`) .expect(404) .end(done) }) })
在这里,您创建了一个无效的ObjectId
并尝试查询数据库以获取与创建的待办事项匹配的内容ObjectId
。测试期望请求返回404错误。
当您运行测试时,它们都应该失败。要让它们通过,请将以下代码添加到您的server.js文件中。
#server/server.js app.get('/todos/:id', (req, res) => { let id = req.params.id // 1 if (!ObjectId.isValid(id)) { // 2 return res.status(404).send('ID is not valid') } Todo.findById(id).then((todo) => { if (!todo) { // 3 return res.status(404).send() } res.send({todo}) //4 }).catch((e) => { res.status(400).send() }) })
从参数中获取待办事项的 ID。
检查***是否有效。如果 ID 无效,则会发送一个错误说明。
findById
如果 ID 有效,请尝试使用该方法查找与该 ID 匹配的待办事项。如果没有找到具有该 ID 的待办事项,则会发送404错误。如果找到待办事项,则发送待办事项。然后你捕获任何发生的错误并发送它。
再次运行测试命令,它应该可以通过。
删除请求测试
对您的DELETE请求的测试将有点像您对 GET 请求的测试。
首先,您要测试是否删除了待办事项。
#server/test/server.test.js describe('DELETE /todos/:id', () => { it('should delete a todo', (done) => { let hexId = todos[0]._id.toHexString() // 1 request(app) .delete(`/todos/${hexId}`) .expect(200) .expect((res) => { expect(res.body.todo._id).toBe(hexId) }) .end((err, res) => { // 2 if (err) { return done(err) } }) Todo.findById(hexId).then((todo) => { // 3 expect(todo.hexId).toNotExist() done() }).catch((e) => done(e)) })
这是上面发生的事情:
您将待办事项的 id 设置为名为 的变量
hexId
。接下来,使用 ID 对待办事项的路径发出DELETE请求。您期望获得200响应,并且获得的待办事项与 的值相匹配hexId
。如果遇到任何错误,则将其返回。
如果没有错误,测试会进一步使用保存为 值的 ID 查询您的数据库
hexId
。由于先前已发送DELETE请求,因此测试预计不存在与该 ID 匹配的待办事项。
接下来,您要测试当请求删除不存在的待办事项时,响应包含404错误。拥有这一点很重要,因为不可能两次删除待办事项。这是测试的外观。
#server/test/server.test.js it('should return 404 if todo is not found', (done) => { let hexId = new ObjectId().toHexString() request(app) .delete(`/todos/${todos/hexId}`) .expect(404) .end(done) })
这会为数据库中不存在的待办事项创建一个 ID。然后发出DELETE请求以删除待办事项。由于待办事项不存在,测试预计会返回404错误。
您想测试使用无效 ID的DELETE是否返回404错误,如下所示。
#server/test/server.test.js it('should return 404 for non-object ids', (done) => { request(app) .delete('/todos/123abc') .expect(404) .end(done) }) })
为了让测试通过,打开你的 server.js 并删除它。
#server/server.js app.delete('/todos/:id', (req, res) => { let id = req.params.id if (!ObjectId.isValid(id)) { return res.status(404).send() } Todo.findByIdAndRemove(id).then((todo) => { if (!todo) { return res.status(404).send() } res.send({todo}) }).catch((e) => { res.status(400).send() }) })
运行命令进行测试:
npm run test
你的测试应该通过了。
结论
至此,您现在知道如何在使用 Node.js 构建 API 时设置测试套件。您已经使用 Mocha、Expect 和 Supertest 来测试 API。这样做的一个好处是您在构建 API 时不需要总是启动 Postman。通过您的测试,您可以了解发生了什么问题。