这篇文章主要是介绍 JavaScript 测试框架 Mocha 在 Node.js Restful API 测试中的使用。
进入主题之前,先说说测试的必要性。
你可以从 repo 获取 CRUD 应用的 Node/Express 样本:
$ git clone https://github.com/mjhea0/node-mocha-chai-tutorial.git
$ git checkout tags/v1
在你获取 v1 app 之后,可以检查一下代码并通过 cURL(或 HTTPie 或 Postman ) 测是每一个 CRUD 函数:
1、添加新的 blobs
2、查看所有的 blobs
3、查看单个的 blob
4、更新单个的 blob
5、删除单个的 blob
这个过程有点乏味,试想如果给应用添加一个新特性你都要手动的按照同样的过程检查一遍会怎么样呢?这不仅会浪费大量时间,而且不是可靠的。因此需要在测试应用程序中建立一个自动化测试框架,因而可以在几秒钟之内运行上百个测试。
于是在运行测试之前,先安装 Mocha:
$ npm install -g mocha@2.3.1
全局安装 mocha,因而能在终端直接运行 mocha
为了构建基本的测试单元,先在项目根目录创建一个名为 test
的文件夹,然后进入到该目录,并添加一个 test-server.js
文件。最终的文件目录看起来应该是酱紫的:
├── package.json
├── server
│ ├── app.js
│ ├── models
│ │ └── blob.js
│ └── routes
│ └── index.js
└── test
└── test-server.js
将下面的代码添加到一个新文件中:
describe('Blobs', function() {
it('should list ALL blobs on /blobs GET');
it('should list a SINGLE blob on /blob/<id> GET');
it('should add a SINGLE blob on /blobs POST');
it('should update a SINGLE blob on /blob/<id> PUT');
it('should delete a SINGLE blob on /blob/<id> DELETE');
});
尽管这是样板文件,但是需要注意到使用的 describe
块和 it
语句。 describe
会按照一种符合逻辑的方式组织测试用例,同时,每一个 it
包含一个独立的测试用例,常用于测试新特性或边缘测试。
为了添加必要的逻辑,我们会利用断言库 Chai@3.2.0 和用于模拟真实 HTTP 请求的 chai-http@1.0.0,并会测试请求的响应是否符合预期。
安装 chai 和 chai-http:
$ npm install chai@3.2.0 chai-http@1.0.0 --save-de
更新 test-server.js
文件:
var chai = require('chai');
var chaiHttp = require('chai-http');
var server = require('../server/app');
var should = chai.should();
chai.use(chaiHttp);
describe('Blobs', function() {
it('should list ALL blobs on /blobs GET');
it('should list a SINGLE blob on /blob/<id> GET');
it('should add a SINGLE blob on /blobs POST');
it('should update a SINGLE blob on /blob/<id> PUT');
it('should delete a SINGLE blob on /blob/<id> DELETE');
});
为了在应用中模拟 HTTP 请求,在 app.js
需要引入新的包:chai
和 chai-http
;同时,需要引入 should
断言库,以便使用 BDD 风格的断言。
Chai 很强大的一方面是允许你按照你的使用习惯来选择断言的风格。阅读断言风格指南了解更多信息。可以在 NPM 和 Github 上找到更多的被包含在 Chai 中的断言库。
现在可以开始写测试了….
首先更新一个 it
语句:
it('should list ALL blobs on /blobs GET', function(done) {
chai.request(server)
.get('/blobs')
.end(function(err, res){
res.should.have.status(200);
done();
});
});
从代码可以看到,我们会给 it
语句传递一个带 done
(一个函数)的异步函数,并在测试用例的最后调用 done()
。测试用例本身很简单:我们模拟一个 get 请求,并断言返回的响应中会包含 HTTP 的 200 状态码。
很简单,是不是?
为了运行测试,只需要运行 mocha ,若运行正常,会看到下列的输出:
$ mocha
Blobs
Connected to Database!
GET /blobs 200 19.621 ms - 2
✓ should list ALL blobs on /blobs GET (43ms)
- should list a SINGLE blob on /blob/<id> GET
- should add a SINGLE blob on /blobs POST
- should update a SINGLE blob on /blob/<id> PUT
- should delete a SINGLE blob on /blob/<id> DELETE
1 passing (72ms)
4 pending
由于单独测试状态码是没有什么意义的,所以需要添加更多的断言:
it('should list ALL blobs on /blobs GET', function(done) {
chai.request(server)
.get('/blobs')
.end(function(err, res){
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('array');
done();
});
});
这是很简单的,因为这些断言就是一些通俗易懂的语句。
基于 indec.js 中的代码,当成功添加一个 Blob 时,将会看到如下的反应信息:
{
"SUCCESS": {
"__v": 0,
"name": "name",
"lastName": "lastname",
"_id": "some-unique-id"
}
}
为了测试 POST 请求,需要编写断言来测试…:
it('should add a SINGLE blob on /blobs POST', function(done) {
chai.request(server)
.post('/blobs')
.send({'name': 'Java', 'lastName': 'Script'})
.end(function(err, res){
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('object');
res.body.should.have.property('SUCCESS');
res.body.SUCCESS.should.be.a('object');
res.body.SUCCESS.should.have.property('name');
res.body.SUCCESS.should.have.property('lastName');
res.body.SUCCESS.should.have.property('_id');
res.body.SUCCESS.name.should.equal('Java');
res.body.SUCCESS.lastName.should.equal('Script');
done();
});
});
需要理解测试中发生了什么?可以在第一个断言之前添加 console.log(res.body)
,运行测试,看看响应体中包含了哪些数据。
到目前为止我们一直在使用主数据库用于测试目的,这并不是一个好主意,因为测试数据会污染数据库。相反,应该利用 test 数据库并在断言之前添加一些假数据。为了达到这个目的,可以使用 beforeEach
和 afterEach
hooks – 正如其名,在运行每个测试之前和之后添加和删除一个文档。
听起来有点难,但是 mocha 很容易就能实现。
在 server
目录下添加一个 _config.js 文件,用于指定不同的数据库 URI:
var config = {};
config.mongoURI = {
development: 'mongodb://localhost/node-testing',
test: 'mongodb://localhost/node-test'
};
module.exports = config;
接下来,更新 app.js,当环境变量 app.settings.env
是 test
(默认值是 development
)时,让其使用 test 数据库:
// *** config file *** //
var config = require('./_config');
// *** mongoose *** ///
mongoose.connect(config.mongoURI[app.settings.env], function(err, res) {
if(err) {
console.log('Error connecting to the database. ' + err);
} else {
console.log('Connected to Database: ' + config.mongoURI[app.settings.env]);
}
});
最后,更新文件引用,并在测试用例中添加 hook:
process.env.NODE_ENV = 'test';
var chai = require('chai');
var chaiHttp = require('chai-http');
var mongoose = require("mongoose");
var server = require('../server/app');
var Blob = require("../server/models/blob");
var should = chai.should();
chai.use(chaiHttp);
describe('Blobs', function() {
Blob.collection.drop();
beforeEach(function(done){
var newBlob = new Blob({
name: 'Bat',
lastName: 'man'
});
newBlob.save(function(err) {
done();
});
});
afterEach(function(done){
Blob.collection.drop();
done();
});
...snip...
现在,在运行每个测试用例之前,test 数据库已被清空,并且会添加一个新的 blob;在每个测试用例之后,test 数据库会在下一个测试运行前被清空。
由于设置了 hook,需要重构之前写的第一个测试:
it('should list ALL blobs on /blobs GET', function(done) {
chai.request(server)
.get('/blobs')
.end(function(err, res){
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('array');
res.body[0].should.have.property('_id');
res.body[0].should.have.property('name');
res.body[0].should.have.property('lastName');
res.body[0].name.should.equal('Bat');
res.body[0].lastName.should.equal('man');
done();
});
});
it('should list a SINGLE blob on /blob/<id> GET', function(done) {
var newBlob = new Blob({
name: 'Super',
lastName: 'man'
});
newBlob.save(function(err, data) {
chai.request(server)
.get('/blob/'+data.id)
.end(function(err, res){
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('object');
res.body.should.have.property('_id');
res.body.should.have.property('name');
res.body.should.have.property('lastName');
res.body.name.should.equal('Super');
res.body.lastName.should.equal('man');
res.body._id.should.equal(data.id);
done();
});
});
});
在这个测试用例中,会先创建一个新的 blob,然后利用新创建的 _id
去模拟请求,并测试返回的响应。
it('should update a SINGLE blob on /blob/<id> PUT', function(done) {
chai.request(server)
.get('/blobs')
.end(function(err, res){
chai.request(server)
.put('/blob/'+res.body[0]._id)
.send({'name': 'Spider'})
.end(function(error, response){
response.should.have.status(200);
response.should.be.json;
response.body.should.be.a('object');
response.body.should.have.property('UPDATED');
response.body.UPDATED.should.be.a('object');
response.body.UPDATED.should.have.property('name');
response.body.UPDATED.should.have.property('_id');
response.body.UPDATED.name.should.equal('Spider');
done();
});
});
});
最后,测试 delete 请求:
it('should delete a SINGLE blob on /blob/<id> DELETE', function(done) {
chai.request(server)
.get('/blobs')
.end(function(err, res){
chai.request(server)
.delete('/blob/'+res.body[0]._id)
.end(function(error, response){
response.should.have.status(200);
response.should.be.json;
response.body.should.be.a('object');
response.body.should.have.property('REMOVED');
response.body.REMOVED.should.be.a('object');
response.body.REMOVED.should.have.property('name');
response.body.REMOVED.should.have.property('_id');
response.body.REMOVED.name.should.equal('Bat');
done();
});
});
});
测试框架 Mocha 实例教程
Testing Node.js With Mocha and Chai
转载请注明:淡忘~浅思 » Node.js 测试:Mocha 和 Chai