原文: http://mherman.org/blog/2015/09/10/testing-node-js-with-mocha-and-chai/#.ViO8oBArIlJ

为什么要测试?

在此之前了解为什么要测试非常重要.

通过下面的地址获取一个Node/Express的简单的CRUD应用:

$ git clone https://github.com/mjhea0/node-mocha-chai-tutorial.git
$ git checkout tags/v1

现在可以通过食用curl(或者Postman)来测试CRUD了:

  1. 添加一个新的blob
  2. 查看所有的blob
  3. 查看一个blob
  4. 更新一个blob
  5. 删除一个blob

这个过程十分乏味. 如果我们在这个应用中没添加一个新功能就需要手动做这样的测试, 不仅会占用大量的时间而且可能还会因为手误或忘记测试某个功能使得测试不可信. 所以我们需要自动测试, 有了自动测试我们可以在几秒钟内测试玩几百个这样的功能.

首先, 安装Mocha:

$ npm install -g mocha@2.3.1

结构

在应用的根目录创建一个文件夹test, 然后在这个test文件夹中创建一个test-server.js. 现在项目的结构如下:

├── package.json
├── server
│ ├── app.js
│ ├── models
│ │ └── blob.js
│ └── routes
│ └── index.js
└── test
└── test-server.js

现在, 在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语句包含了每个独立的测试用例.

逻辑

Chai是一个assertion库, 我们使用chai-http做一些HTTP请求, 然后测试响应:

使用下面的命令同时安装chai和chai-http:

$ npm install chai@3.2.0 chai-http@1.0.0 --save-dev

更新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');
});

现在可以写测试了

测试-GET(all)

更新第一个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();
});
});

我们传递了一个只用一个参数(done function)的匿名函数给it. 这个匿名函数的最后一行是done(). 这个测试十分简单, Get /blobs 判断响应的http status code是否是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

因为简单的判断HTTP状态码对我们判断是否返回整个blobs来说意义不大, 现在来添加更多的断言:

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

  

测试-POST

当添加blob成功, 响应如下:

{
"SUCCESS": {
"__v": 0,
"name": "name",
"lastName": "lastname",
"_id": "some-unique-id"
}
}

现在我们来写测试:

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

  

Hooks

专门有一个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文件:

// *** 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]);
}
});

最后更新测试脚本:

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...

现在, 每个单元测试(it)运行之前数据库会被清空, 接下来会添加一个blob. 运行之后有会清空数据库.

测试-GET(all)

有了hooks的设置后, 我们修改get(all)的单元测试:

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

测试-GET(single)

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

测试-PUT

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

测试-删除

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

  

[译]Testing Node.js With Mocha and Chai的更多相关文章

  1. [Node.js] Testing ES6 Promises in Node.js using Mocha and Chai

    Writing great ES6 style Promises for Node.js is only half the battle. Your great modules must includ ...

  2. 【译】 Node.js v0.12的新特性 -- 性能优化

    原文: https://strongloop.com/strongblog/performance-node-js-v-0-12-whats-new/ January 21, 2014/in Comm ...

  3. 【译】 Node.js v0.12的新特性 -- Cluster模式采用Round-Robin负载均衡

    原文:https://strongloop.com/strongblog/whats-new-in-node-js-v0-12-cluster-round-robin-load-balancing 本 ...

  4. (译+注解)node.js的C++扩展入门

    声明:本文主要翻译自node.js addons官方文档.部分解释为作者自己添加. 编程环境: 1. 操作系统 Mac OS X 10.9.51. node.js v4.4.22. npm v3.9. ...

  5. 【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick

    [摘要] 官网博文翻译,nodejs中的定时器 示例代码托管在:http://www.github.com/dashnowords/blogs 原文地址:https://nodejs.org/en/d ...

  6. 【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick

    目录 Event Loop 是什么? Event Loop 基本解释 事件循环阶段概览 事件循环细节 timers pending callbacks poll阶段 check close callb ...

  7. 带你入门带你飞Ⅱ 使用Mocha + Chai + SuperTest测试Restful API in node.js

    目录 1. 简介 2. 准备开始 3. Restful API测试实战 Example 1 - GET Example 2 - Post Example 3 - Put Example 4 - Del ...

  8. Practical Node.js (2018版) 第3章:测试/Mocha.js, Chai.js, Expect.js

    TDD and BDD for Node.js with Mocha TDD测试驱动开发.自动测试代码. BDD: behavior-driven development行为驱动开发,基于TDD.一种 ...

  9. 10+ 最佳的 Node.js 教程和实例

    如果你正在找Node.js的学习资料及指南,那么请继续(阅读),我们的教程将会覆盖即时聊天应用.API服务编写.投票问卷应用.人物投票APP.社交授权. Node.js on Raspberry Pi ...

随机推荐

  1. Chrome浏览器设置默认编码

    设置-->高级设置-->网络内容-->自定义字体(滚动条拉到最底部)-->编码

  2. Java基础知识笔记(三:文件与数据流)

    一.输入流与输出流 输入流将数据从文件.标准输入或其他外部输入设备中加载到内存.输出流的作用则刚好相反,即将在内存中的数据保存到文件中,或传输给输出设备.输入流在Java语言中对应于抽象类java.i ...

  3. [WPF系列]-TreeView的常用事项

    引言 项目经常会用Treeview来组织一些具有层级结构的数据,本节就将项目使用Treeview常见的问题作一个总结. DataBinding数据绑定 DataTemplate自定义 <Hier ...

  4. STM32电机控制器小心得

    首先声明的是本人刚刚大学毕业进入电机控制这个行业,以前在学校也做过类似51的实验,然而在工作中发现那些东西是皮毛的不能再皮毛,我现在在公司也算是一个实习生,主要工作是改各厂家对控制器的功能需求,(其实 ...

  5. Linux 系统中僵尸进程

    Linux 系统中僵尸进程和现实中僵尸(虽然我也没见过)类似,虽然已经死了,但是由于没人给它们收尸,还能四处走动.僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸.配图源 ...

  6. 【2016-11-13】【坚持学习】【Day25】【Oracle 查询某一字段最大值】

    partition 分组order by 以哪一个字段排序 select n.* ,row_number()over(partition by n.type order by n.createtime ...

  7. NOIP2012 普及组 T3 摆花——S.B.S.

    题目描述 小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆.通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号.为了在门口展出更多种花,规定第i种花不能超过ai盆,摆花时 ...

  8. 一维码:EAN-13码的识别

    1.一维码简述: 一维条码是一种能用于信息编码和信息自动识别的标准符号,是由一组宽度不同的黑白符号按一定规则交替排列编码组成的图形符号,用于表示一定的信息. 码制指条码符号的类型,不同的类型有不同的编 ...

  9. Unity新功能|全息模拟器

    http://forum.china.unity3d.com/thread-21539-1-1.html

  10. 使用 v-cloak 防止页面加载时出现 vuejs 的变量名

    使用 vuejs 做了一个简单的功能页面,逻辑是,页面加载后获取当前的经纬度,然后通过 ajax 从后台拉取附近的小区列表.但是 bug 出现了,在显示小区列表之前,会闪现小区名对应的 vuejs 变 ...