使用 Mocha 和 Chai 测试 Node RESTful API

介绍

我仍然记得终于能够在节点中写出一个更大的应用程序的后端部分的满足感,我相信你们中的许多人也会这样做。

然后?我们需要确保我们的应用程序以我们预期的方式行为,并强烈建议的方法之一是软件测试。软件测试在系统中添加新功能时非常有用:已经设置了可以使用单个命令运行的测试环境有助于确定新功能是否会引入新错误。

在过去,我们已经在 Node API Authentication with JSON Web Tokens and Passport上工作过。

在本教程中,我们将用 Node.js 编写一个简单的 RESTful API,并使用 MochaChai来编写测试。

像往常一样,您可以在整个教程中逐步构建应用程序,或者直接在 github上获取。

Mocha:测试环境

Mocha 是 Node.js 的 javascript 框架,允许非同步测试,假设它提供了我们可以使用我们最喜欢的声明库来测试代码的环境。

mocha-homepage.

Mocha带有大量伟大的功能,网站显示了一个漫长的列表,但这里是我最喜欢的:

  • 简单的协同支持,包括承诺 *协同测试时间支持
  • 之前,之后,之前,每个接口之后(非常有用来清理每个测试的环境!)
  • 使用您想要的任何声明库,Chai在我们的教程中

标签: 宣言图书馆

因此,在Mocha中,我们实际上有做测试的环境,但我们如何测试HTTP调用?此外,我们如何测试GET请求是否实际上返回我们期望的JSON文件,鉴于定义的输入?我们需要一个声明库,这就是为什么mocha是不够的。

所以这里是 Chai,当前教程的声明库:

chai homepage

Chai闪耀在选择我们喜欢的界面的自由:应该,期望,声明他们都可用。我个人使用应该,但你可以自由地检查它 API并切换到其他两个。

前提条件

  • Node.js:对node.js的基本理解,建议我不要过于深入细节地构建一个RESTful API
  • POSTMAN用于快速向API提出HTTP请求( _)* _ES6语法:我决定使用Node(6..)的最新版本,该版本具有最高的ES6功能集成,以便更好地读取代码。如果你不熟悉ES6,你可以看看一些伟大的苏格兰文章(Pt.1, Pt.2Pt.3)关于它,但不要担心我会花几句话,每当我们讲述一些异国语法或声明 )

是时候开启我们的书店了!

项目设置

董事会结构

以下是我们API的项目目录,您必须之前看到的内容:

 1-- controllers 
 2---- models
 3------ book.js
 4---- routes
 5------ book.js
 6-- config
 7---- default.json
 8---- dev.json
 9---- test.json
10-- test
11---- book.js
12package.json
13server.json

请注意包含 3 个 JSON 文件的 `/config’ 文件夹:正如其名称所示,它们包含针对特定目的的特定配置。

在本教程中,我们将切换两个数据库,一个用于开发和一个用于测试目的,因此文件包含 mongodb URI 在 JSON 格式:

1[label dev.json AND default.json]
2{ "DBHost": "YOUR_DB_URI" }
1[label test.json]
2{ "DBHost": "YOUR_TEST_DB_URI" }

NB: default.json 是可选的,但让我强调 Config 目录中的文件是从它开始加载的. 有关配置文件的更多信息(config 目录,文件顺序,文件格式等)请参阅此 链接

最后,注意/test/book.js,这就是我们要写我们的测试的地方!

标签:JSON

创建package.json文件并粘贴以下代码:

 1{
 2  "name": "bookstore",
 3  "version": "1.0.0",
 4  "description": "A bookstore API",
 5  "main": "server.js",
 6  "author": "Sam",
 7  "license": "ISC",
 8  "dependencies": {
 9    "body-parser": "^1.15.1",
10    "config": "^1.20.1",
11    "express": "^4.13.4",
12    "mongoose": "^4.4.15",
13    "morgan": "^1.7.0"
14  },
15  "devDependencies": {
16    "chai": "^3.5.0",
17    "chai-http": "^2.0.1",
18    "mocha": "^2.4.5"
19  },
20  "scripts": {
21    "start": "SET NODE_ENV=dev && node server.js",
22    "test": "mocha --timeout 10000"
23  }
24}

再一次,配置不应该让任何使用 node.js 编写多个服务器的人感到惊讶,测试相关的包 mocha, chai, chai-http 都保存在 dev 依赖中(从命令行标记 --save-dev),而脚本属性允许运行服务器的两种不同的方式。

要运行mocha,我添加了旗帜 --timeout 10000,因为我从托管在mongolab上的数据库中获取数据,所以默认的2秒可能不够。

你通过了教程的无聊部分,现在是时候写服务器并测试它了。

服务器

主要

让我们在项目的根中创建server.js文件,并粘贴以下代码:

 1let express = require('express');
 2let app = express();
 3let mongoose = require('mongoose');
 4let morgan = require('morgan');
 5let bodyParser = require('body-parser');
 6let port = 8080;
 7let book = require('./app/routes/book');
 8let config = require('config'); //we load the db location from the JSON files
 9//db options
10let options = { 
11                server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }, 
12                replset: { socketOptions: { keepAlive: 1, connectTimeoutMS : 30000 } } 
13              }; 
14
15//db connection      
16mongoose.connect(config.DBHost, options);
17let db = mongoose.connection;
18db.on('error', console.error.bind(console, 'connection error:'));
19
20//don't show the log when it is test
21if(config.util.getEnv('NODE_ENV') !== 'test') {
22    //use morgan to log at command line
23    app.use(morgan('combined')); //'combined' outputs the Apache style LOGs
24}
25
26//parse application/json and look for raw text                                        
27app.use(bodyParser.json());                                     
28app.use(bodyParser.urlencoded({extended: true}));               
29app.use(bodyParser.text());                                    
30app.use(bodyParser.json({ type: 'application/json'}));  
31
32app.get("/", (req, res) => res.json({message: "Welcome to our Bookstore!"}));
33
34app.route("/book")
35    .get(book.getBooks)
36    .post(book.postBook);
37app.route("/book/:id")
38    .get(book.getBook)
39    .delete(book.deleteBook)
40    .put(book.updateBook);
41
42app.listen(port);
43console.log("Listening on port " + port);
44
45module.exports = app; // for testing

以下是关键概念:

  • 我们需要模块配置来访问命名为 NODE_ENV 内容的配置文件,以获取 db 连接的 mongo db URI 参数。这有助于我们通过测试对我们应用程序未来的用户隐藏的另一个数据库来保持真实数据库的清洁 *环境变量 NODE_ENV 被测试对 test 禁用 morgan在命令行中登录,或者它会干扰测试输出 *最后一行代码导出服务器进行测试
  • 使用让变量定义通知使变量连接到最近的封锁区块或全球如果任何区块

剩余的代码行不是什么新鲜事,我们只是通过要求所有必要的模块,定义与服务器通信的标题选项,打破特定根,最终让服务器在定义的端口上聆听。

模式与路线

创建一个名为book.js的文件在/app/model/中,并粘贴以下代码:

 1let mongoose = require('mongoose');
 2let Schema = mongoose.Schema;
 3
 4//book schema definition
 5let BookSchema = new Schema(
 6  {
 7    title: { type: String, required: true },
 8    author: { type: String, required: true },
 9    year: { type: Number, required: true },
10    pages: { type: Number, required: true, min: 1 },
11    createdAt: { type: Date, default: Date.now },    
12  }, 
13  { 
14    versionKey: false
15  }
16);
17
18// Sets the createdAt parameter equal to the current time
19BookSchema.pre('save', next => {
20  now = new Date();
21  if(!this.createdAt) {
22    this.createdAt = now;
23  }
24  next();
25});
26
27//Exports the BookSchema for use elsewhere.
28module.exports = mongoose.model('book', BookSchema);

我们的书架有一个标题,作者,页数,出版年份和创建日期在db. 我将版本Key设置为假,因为它对于教程的目的是无用的。

NB:.pre() 函数中的异国情调呼叫语法是一个箭头函数,一个函数具有较短的语法,根据 MDN _ 的定义,它`语法上连接这个值(不连接它自己的这个,参数,超级或 new.target)。

好吧,几乎所有我们需要知道的模型,所以让我们转向路线。

/app/routes/ 中,创建一个名为 book.js 的文件,并粘贴以下代码:

 1let mongoose = require('mongoose');
 2let Book = require('../models/book');
 3
 4/*
 5 * GET /book route to retrieve all the books.
 6 */
 7function getBooks(req, res) {
 8    //Query the DB and if no errors, send all the books
 9    let query = Book.find({});
10    query.exec((err, books) => {
11        if(err) res.send(err);
12        //If no errors, send them back to the client
13        res.json(books);
14    });
15}
16
17/*
18 * POST /book to save a new book.
19 */
20function postBook(req, res) {
21    //Creates a new book
22    var newBook = new Book(req.body);
23    //Save it into the DB.
24    newBook.save((err,book) => {
25        if(err) {
26            res.send(err);
27        }
28        else { //If no errors, send it back to the client
29            res.json({message: "Book successfully added!", book });
30        }
31    });
32}
33
34/*
35 * GET /book/:id route to retrieve a book given its id.
36 */
37function getBook(req, res) {
38    Book.findById(req.params.id, (err, book) => {
39        if(err) res.send(err);
40        //If no errors, send it back to the client
41        res.json(book);
42    });        
43}
44
45/*
46 * DELETE /book/:id to delete a book given its id.
47 */
48function deleteBook(req, res) {
49    Book.remove({_id : req.params.id}, (err, result) => {
50        res.json({ message: "Book successfully deleted!", result });
51    });
52}
53
54/*
55 * PUT /book/:id to updatea a book given its id
56 */
57function updateBook(req, res) {
58    Book.findById({_id: req.params.id}, (err, book) => {
59        if(err) res.send(err);
60        Object.assign(book, req.body).save((err, book) => {
61            if(err) res.send(err);
62            res.json({ message: 'Book updated!', book });
63        });    
64    });
65}
66
67//export all the functions
68module.exports = { getBooks, postBook, getBook, deleteBook, updateBook };

以下是关键概念:

  • 路径仅仅是标准路径,GET,POST,DELETE,PUT来对我们的数据执行CRUD操作
  • 在函数 updatedBook() 中,我们使用了 Object.assign,这是一种在 ES6 中引入的新函数,在这种情况下,它将书籍的共同属性与 req.body 替代,而其他属性不受影响
  • 最后,我们使用更快的语法来导出对象,该函数将密钥和值配对,以避免无用的重复。

我们完成了这个部分,实际上我们有一个工作应用程序!

天真的考验

现在,让我们运行应用程序,打开POSTMAN,将HTTP请求发送到服务器,并检查一切是否按预期工作。

在命令行中运行

1npm start

读 / 书

在 POSTMAN 中运行 GET 请求,假设数据库包含书籍,以下是结果:

服务器正确返回了我的数据库中的图书列表。

文章 / 书

让我们在服务器上添加一本书和POST:

看起来这本书被完美地添加了,服务器返回了这本书,并在我们的书店添加了确认信息,这是真的吗?让我们发送另一个GET请求,这里是结果:

很棒它工作!

图书馆 / 图书馆 / id

让我们通过更改页面来更新一本书,并检查结果:

很棒! PUT 似乎也在工作,所以让我们发送另一个 GET 请求来检查所有列表:

一切都走得很顺利......

图书馆 / 图书馆 / id

现在让我们通过在 GET 请求中发送 id 来获得一个单一的书籍,然后删除它:

當它返回正確的書,讓我們現在嘗試刪除它:

删除 /book/ :id

以下是对服务器的DELETE请求的结果:

即使是最后一个请求工作顺利,我们不需要对另一个 GET 请求进行 doublecheck,因为我们正在向客户端发送一些来自mongo(结果属性)的信息,声明该书实际上被删除。

通过使用 POSTMAN 进行一些测试,应用程序发生了像预期的那样正确的行为吗?那么,你会把它拍给你的客户吗?

讓我為你解答:NO!!!

我们的是我称之为天真测试,因为我们只是尝试了一些操作而没有测试可能发生的奇怪情况:没有一些预期数据的邮件请求,错误的ID作为参数的DELETE,甚至没有ID来命名少数。

显然,这是一个简单的应用程序,如果我们很幸运,我们没有引入任何类型的错误,但现实世界的应用程序呢?此外,我们花了时间与POSTMAN运行一些测试HTTP请求,所以如果有一天我们不得不更改其中一个代码? 用POSTMAN再次测试它们? 您是否开始意识到这不是一个敏捷的方法?

这是没有什么,但你可能遇到的很少的情况,你已经遇到在你的开发者之旅,幸运的是,我们有工具来创建测试,总是可用,可以通过单个命令行启动。

让我们做一些更好的东西来测试我们的应用程序!

更好的测试

首先,让我们在/test中创建一个名为book.js的文件,并粘贴以下代码:

 1//During the test the env variable is set to test
 2process.env.NODE_ENV = 'test';
 3
 4let mongoose = require("mongoose");
 5let Book = require('../app/models/book');
 6
 7//Require the dev-dependencies
 8let chai = require('chai');
 9let chaiHttp = require('chai-http');
10let server = require('../server');
11let should = chai.should();
12
13chai.use(chaiHttp);
14//Our parent block
15describe('Books', () => {
16    beforeEach((done) => { //Before each test we empty the database
17        Book.remove({}, (err) => { 
18           done();           
19        });        
20    });
21/*
22  * Test the /GET route
23  */
24  describe('/GET book', () => {
25      it('it should GET all the books', (done) => {
26        chai.request(server)
27            .get('/book')
28            .end((err, res) => {
29                  res.should.have.status(200);
30                  res.body.should.be.a('array');
31                  res.body.length.should.be.eql(0);
32              done();
33            });
34      });
35  });
36
37});

哇,这是很多新东西,让我们挖掘进去:

您一定注意到我们设置了 NODE_ENV 变量来测试,这样我们就改变了要加载的配置文件,这样服务器就可以连接到测试数据库并避免在 cmd 中出现 morgan 日志 2。我们需要 dev 依赖模块和服务器本身(您记得我们用 module.exports 导出了它吗?) 3。我们通过运行 chai.should() 来定义 should 来对 HTTP 请求结果进行测试,然后我们告诉 chai 使用 chai HTTP

因此,它从描述代码块开始,以便更好地组织你的声明,这个组织将反映在命令行的输出中,正如我们稍后所看到的那样。

beforeEach是一个代码块,它将在同一级别的描述块之前运行,为什么我们这样做?我们将从数据库中删除任何书籍,以便每次运行测试时开始使用一个空的书店。

测试 / GET 路线

这里是第一个测试,chai将向服务器执行一个GET请求,res变量上的声明将满足或拒绝块的第一个参数 it should GET所有书籍

  1. 状态 200.
  2. 结果应该是一个数组
  3. 由于书店是空的,我们假定长度等于0

请注意 should 声明的语法是非常有意义的,因为它类似于自然语言声明。

现在,在命令行中运行:

「javaScript npm 測試」

这里是产量:

测试通过,输出反映了我们用描述块组织代码的方式。

测试 / POST 路线

现在让我们检查我们的强大是我们的API,假设我们正在尝试添加到服务器的缺失页面字段的书籍:服务器不应该响应正确的错误消息。

将以下代码复制并粘贴到测试文件中:

 1process.env.NODE_ENV = 'test';
 2
 3let mongoose = require("mongoose");
 4let Book = require('../app/models/book');
 5
 6let chai = require('chai');
 7let chaiHttp = require('chai-http');
 8let server = require('../server');
 9let should = chai.should();
10
11chai.use(chaiHttp);
12
13describe('Books', () => {
14    beforeEach((done) => {
15        Book.remove({}, (err) => { 
16           done();           
17        });        
18    });
19  describe('/GET book', () => {
20      it('it should GET all the books', (done) => {
21        chai.request(server)
22            .get('/book')
23            .end((err, res) => {
24                  res.should.have.status(200);
25                  res.body.should.be.a('array');
26                  res.body.length.should.be.eql(0);
27              done();
28            });
29      });
30  });
31  /*
32  * Test the /POST route
33  */
34  describe('/POST book', () => {
35      it('it should not POST a book without pages field', (done) => {
36          let book = {
37              title: "The Lord of the Rings",
38              author: "J.R.R. Tolkien",
39              year: 1954
40          }
41        chai.request(server)
42            .post('/book')
43            .send(book)
44            .end((err, res) => {
45                  res.should.have.status(200);
46                  res.body.should.be.a('object');
47                  res.body.should.have.property('errors');
48                  res.body.errors.should.have.property('pages');
49                  res.body.errors.pages.should.have.property('kind').eql('required');
50              done();
51            });
52      });
53
54  });
55});

在这里我们添加了对不完整的 / POST 请求的测试,让我们分析声明:

  1. 状态应该是 200
  2. 响应体应该是对象
  3. 一个体属性应该是错误
  4. 错误应该有缺少的字段页面作为属性
  5. 最后,页面应该有等于 required 的属性类型,以突出我们从服务器获得负答案的原因

NB 注意,我们通过 .send() 函数发送书籍和 POST 请求。

让我们再次运行相同的命令,这里是输出:

是的,我们的测试是正确的!

在写一个新的测试之前,让我澄清两点:

  1. 首先,为什么服务器响应是这样结构化的? 如果您阅读 / POST 路径的回复函数,您会注意到,如果缺少所需的字段,服务器会从 mongoose 发送错误消息。 尝试使用 POSTMAN 并检查响应
  2. 如果缺少的字段,我们仍然返回 200 的状态,这是为了简单,因为我们只是学习测试我们的路径。 然而,我建议返回 _206 部分内容的状态。

这一次,让我们发送一本包含所有所需字段的书籍. 复制并粘贴以下代码到测试文件:

 1process.env.NODE_ENV = 'test';
 2
 3let mongoose = require("mongoose");
 4let Book = require('../app/models/book');
 5
 6let chai = require('chai');
 7let chaiHttp = require('chai-http');
 8let server = require('../server');
 9let should = chai.should();
10
11chai.use(chaiHttp);
12
13describe('Books', () => {
14    beforeEach((done) => {
15        Book.remove({}, (err) => { 
16           done();           
17        });        
18    });
19  describe('/GET book', () => {
20      it('it should GET all the books', (done) => {
21        chai.request(server)
22            .get('/book')
23            .end((err, res) => {
24                  res.should.have.status(200);
25                  res.body.should.be.a('array');
26                  res.body.length.should.be.eql(0);
27              done();
28            });
29      });
30  });
31  /*
32  * Test the /POST route
33  */
34  describe('/POST book', () => {
35      it('it should not POST a book without pages field', (done) => {
36          let book = {
37              title: "The Lord of the Rings",
38              author: "J.R.R. Tolkien",
39              year: 1954
40          }
41        chai.request(server)
42            .post('/book')
43            .send(book)
44            .end((err, res) => {
45                  res.should.have.status(200);
46                  res.body.should.be.a('object');
47                  res.body.should.have.property('errors');
48                  res.body.errors.should.have.property('pages');
49                  res.body.errors.pages.should.have.property('kind').eql('required');
50              done();
51            });
52      });
53      it('it should POST a book ', (done) => {
54          let book = {
55              title: "The Lord of the Rings",
56              author: "J.R.R. Tolkien",
57              year: 1954,
58              pages: 1170
59          }
60        chai.request(server)
61            .post('/book')
62            .send(book)
63            .end((err, res) => {
64                  res.should.have.status(200);
65                  res.body.should.be.a('object');
66                  res.body.should.have.property('message').eql('Book successfully added!');
67                  res.body.book.should.have.property('title');
68                  res.body.book.should.have.property('author');
69                  res.body.book.should.have.property('pages');
70                  res.body.book.should.have.property('year');
71              done();
72            });
73      });
74  });
75});

这一次,我们期待一个返回的对象,一个消息说我们成功地添加了这本书和这本书本身(记住POSTMAN?).你现在应该非常熟悉我所做的声明,所以没有必要深入细节。

柔软 ~

测试 /GET/:ID路线

现在让我们创建一本书,将其保存到数据库中,然后使用 ID 将 GET 请求发送到服务器。

 1process.env.NODE_ENV = 'test';
 2
 3let mongoose = require("mongoose");
 4let Book = require('../app/models/book');
 5
 6let chai = require('chai');
 7let chaiHttp = require('chai-http');
 8let server = require('../server');
 9let should = chai.should();
10
11chai.use(chaiHttp);
12
13describe('Books', () => {
14    beforeEach((done) => {
15        Book.remove({}, (err) => { 
16           done();           
17        });        
18    });
19  describe('/GET book', () => {
20      it('it should GET all the books', (done) => {
21            chai.request(server)
22            .get('/book')
23            .end((err, res) => {
24                  res.should.have.status(200);
25                  res.body.should.be.a('array');
26                  res.body.length.should.be.eql(0);
27              done();
28            });
29      });
30  });
31  describe('/POST book', () => {
32      it('it should not POST a book without pages field', (done) => {
33          let book = {
34              title: "The Lord of the Rings",
35              author: "J.R.R. Tolkien",
36              year: 1954
37          }
38            chai.request(server)
39            .post('/book')
40            .send(book)
41            .end((err, res) => {
42                  res.should.have.status(200);
43                  res.body.should.be.a('object');
44                  res.body.should.have.property('errors');
45                  res.body.errors.should.have.property('pages');
46                  res.body.errors.pages.should.have.property('kind').eql('required');
47              done();
48            });
49      });
50      it('it should POST a book ', (done) => {
51          let book = {
52              title: "The Lord of the Rings",
53              author: "J.R.R. Tolkien",
54              year: 1954,
55              pages: 1170
56          }
57            chai.request(server)
58            .post('/book')
59            .send(book)
60            .end((err, res) => {
61                  res.should.have.status(200);
62                  res.body.should.be.a('object');
63                  res.body.should.have.property('message').eql('Book successfully added!');
64                  res.body.book.should.have.property('title');
65                  res.body.book.should.have.property('author');
66                  res.body.book.should.have.property('pages');
67                  res.body.book.should.have.property('year');
68              done();
69            });
70      });
71  });
72 /*
73  * Test the /GET/:id route
74  */
75  describe('/GET/:id book', () => {
76      it('it should GET a book by the given id', (done) => {
77          let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });
78          book.save((err, book) => {
79              chai.request(server)
80            .get('/book/' + book.id)
81            .send(book)
82            .end((err, res) => {
83                  res.should.have.status(200);
84                  res.body.should.be.a('object');
85                  res.body.should.have.property('title');
86                  res.body.should.have.property('author');
87                  res.body.should.have.property('pages');
88                  res.body.should.have.property('year');
89                  res.body.should.have.property('_id').eql(book.id);
90              done();
91            });
92          });
93
94      });
95  });
96});

通过声明,我们确保服务器返回了所有字段和正确的书籍测试两个 ids 一起。

你是否注意到,通过在独立区块中测试单个路径,我们可以提供非常清晰的输出? 而且,这不是那么有效吗? 我们写了几个测试,可以一次又一次地重复使用单个命令行。

测试 /PUT/:id 路线

测试我们一本书的更新时间,我们先保存本书,然后更新它出版的年份,然后复制并粘贴以下代码:

  1process.env.NODE_ENV = 'test';
  2
  3let mongoose = require("mongoose");
  4let Book = require('../app/models/book');
  5
  6let chai = require('chai');
  7let chaiHttp = require('chai-http');
  8let server = require('../server');
  9let should = chai.should();
 10
 11chai.use(chaiHttp);
 12
 13describe('Books', () => {
 14    beforeEach((done) => {
 15        Book.remove({}, (err) => { 
 16           done();           
 17        });        
 18    });
 19  describe('/GET book', () => {
 20      it('it should GET all the books', (done) => {
 21            chai.request(server)
 22            .get('/book')
 23            .end((err, res) => {
 24                  res.should.have.status(200);
 25                  res.body.should.be.a('array');
 26                  res.body.length.should.be.eql(0);
 27              done();
 28            });
 29      });
 30  });
 31  describe('/POST book', () => {
 32      it('it should not POST a book without pages field', (done) => {
 33          let book = {
 34              title: "The Lord of the Rings",
 35              author: "J.R.R. Tolkien",
 36              year: 1954
 37          }
 38            chai.request(server)
 39            .post('/book')
 40            .send(book)
 41            .end((err, res) => {
 42                  res.should.have.status(200);
 43                  res.body.should.be.a('object');
 44                  res.body.should.have.property('errors');
 45                  res.body.errors.should.have.property('pages');
 46                  res.body.errors.pages.should.have.property('kind').eql('required');
 47              done();
 48            });
 49      });
 50      it('it should POST a book ', (done) => {
 51          let book = {
 52              title: "The Lord of the Rings",
 53              author: "J.R.R. Tolkien",
 54              year: 1954,
 55              pages: 1170
 56          }
 57            chai.request(server)
 58            .post('/book')
 59            .send(book)
 60            .end((err, res) => {
 61                  res.should.have.status(200);
 62                  res.body.should.be.a('object');
 63                  res.body.should.have.property('message').eql('Book successfully added!');
 64                  res.body.book.should.have.property('title');
 65                  res.body.book.should.have.property('author');
 66                  res.body.book.should.have.property('pages');
 67                  res.body.book.should.have.property('year');
 68              done();
 69            });
 70      });
 71  });
 72  describe('/GET/:id book', () => {
 73      it('it should GET a book by the given id', (done) => {
 74          let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });
 75          book.save((err, book) => {
 76              chai.request(server)
 77            .get('/book/' + book.id)
 78            .send(book)
 79            .end((err, res) => {
 80                  res.should.have.status(200);
 81                  res.body.should.be.a('object');
 82                  res.body.should.have.property('title');
 83                  res.body.should.have.property('author');
 84                  res.body.should.have.property('pages');
 85                  res.body.should.have.property('year');
 86                  res.body.should.have.property('_id').eql(book.id);
 87              done();
 88            });
 89          });
 90
 91      });
 92  });
 93 /*
 94  * Test the /PUT/:id route
 95  */
 96  describe('/PUT/:id book', () => {
 97      it('it should UPDATE a book given the id', (done) => {
 98          let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})
 99          book.save((err, book) => {
100                chai.request(server)
101                .put('/book/' + book.id)
102                .send({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1950, pages: 778})
103                .end((err, res) => {
104                      res.should.have.status(200);
105                      res.body.should.be.a('object');
106                      res.body.should.have.property('message').eql('Book updated!');
107                      res.body.book.should.have.property('year').eql(1950);
108                  done();
109                });
110          });
111      });
112  });
113});

我们想确保消息是 the correct Book updated! 一个,并且字段实际上是更新的。

好吧,我们接近终点,我们仍然需要测试DELETE路线。

测试 /DELETE/:id 路线

模式与以前的测试类似,我们先存储一本书,删除它并对响应进行测试。

  1process.env.NODE_ENV = 'test';
  2
  3let mongoose = require("mongoose");
  4let Book = require('../app/models/book');
  5
  6let chai = require('chai');
  7let chaiHttp = require('chai-http');
  8let server = require('../server');
  9let should = chai.should();
 10
 11chai.use(chaiHttp);
 12
 13describe('Books', () => {
 14    beforeEach((done) => {
 15        Book.remove({}, (err) => { 
 16           done();           
 17        });        
 18    });
 19  describe('/GET book', () => {
 20      it('it should GET all the books', (done) => {
 21            chai.request(server)
 22            .get('/book')
 23            .end((err, res) => {
 24                  res.should.have.status(200);
 25                  res.body.should.be.a('array');
 26                  res.body.length.should.be.eql(0);
 27              done();
 28            });
 29      });
 30  });
 31  describe('/POST book', () => {
 32      it('it should not POST a book without pages field', (done) => {
 33          let book = {
 34              title: "The Lord of the Rings",
 35              author: "J.R.R. Tolkien",
 36              year: 1954
 37          }
 38            chai.request(server)
 39            .post('/book')
 40            .send(book)
 41            .end((err, res) => {
 42                  res.should.have.status(200);
 43                  res.body.should.be.a('object');
 44                  res.body.should.have.property('errors');
 45                  res.body.errors.should.have.property('pages');
 46                  res.body.errors.pages.should.have.property('kind').eql('required');
 47              done();
 48            });
 49      });
 50      it('it should POST a book ', (done) => {
 51          let book = {
 52              title: "The Lord of the Rings",
 53              author: "J.R.R. Tolkien",
 54              year: 1954,
 55              pages: 1170
 56          }
 57            chai.request(server)
 58            .post('/book')
 59            .send(book)
 60            .end((err, res) => {
 61                  res.should.have.status(200);
 62                  res.body.should.be.a('object');
 63                  res.body.should.have.property('message').eql('Book successfully added!');
 64                  res.body.book.should.have.property('title');
 65                  res.body.book.should.have.property('author');
 66                  res.body.book.should.have.property('pages');
 67                  res.body.book.should.have.property('year');
 68              done();
 69            });
 70      });
 71  });
 72  describe('/GET/:id book', () => {
 73      it('it should GET a book by the given id', (done) => {
 74          let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });
 75          book.save((err, book) => {
 76              chai.request(server)
 77            .get('/book/' + book.id)
 78            .send(book)
 79            .end((err, res) => {
 80                  res.should.have.status(200);
 81                  res.body.should.be.a('object');
 82                  res.body.should.have.property('title');
 83                  res.body.should.have.property('author');
 84                  res.body.should.have.property('pages');
 85                  res.body.should.have.property('year');
 86                  res.body.should.have.property('_id').eql(book.id);
 87              done();
 88            });
 89          });
 90
 91      });
 92  });
 93  describe('/PUT/:id book', () => {
 94      it('it should UPDATE a book given the id', (done) => {
 95          let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})
 96          book.save((err, book) => {
 97                chai.request(server)
 98                .put('/book/' + book.id)
 99                .send({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1950, pages: 778})
100                .end((err, res) => {
101                      res.should.have.status(200);
102                      res.body.should.be.a('object');
103                      res.body.should.have.property('message').eql('Book updated!');
104                      res.body.book.should.have.property('year').eql(1950);
105                  done();
106                });
107          });
108      });
109  });
110 /*
111  * Test the /DELETE/:id route
112  */
113  describe('/DELETE/:id book', () => {
114      it('it should DELETE a book given the id', (done) => {
115          let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})
116          book.save((err, book) => {
117                chai.request(server)
118                .delete('/book/' + book.id)
119                .end((err, res) => {
120                      res.should.have.status(200);
121                      res.body.should.be.a('object');
122                      res.body.should.have.property('message').eql('Book successfully deleted!');
123                      res.body.result.should.have.property('ok').eql(1);
124                      res.body.result.should.have.property('n').eql(1);
125                  done();
126                });
127          });
128      });
129  });
130});

再次,服务器返回来自mongoose的消息和属性,我们声称,所以让我们检查输出:

很棒,我们的测试都是积极的,我们有很好的基础继续测试我们的路线,以更复杂的说法。

恭喜你完成了教程!

结论

在本教程中,我们面临了测试我们的路线的问题,以便为我们的用户提供稳定的体验。

我们通过创建一个RESTful API的所有步骤,通过POSTMAN进行天真测试,然后提出更好的方法来测试,实际上是教程的主题。

总是花一些时间进行测试,以确保服务器尽可能可靠,但不幸的是,它经常被低估。

在教程中,我们还讨论了代码测试的几个好处,这将为测试驱动开发(TDD)等更先进的主题打开大门。

Published At
Categories with 技术
Tagged with
comments powered by Disqus