原文: 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. 理解ThreadLocal(之一)

    ThreadLocal是什么 在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编 ...

  2. 洛谷CON1041 NOIP模拟赛一试

    A T2-power of 2 题目描述 是一个十分特殊的式子. 例如: n=0时 =2 然而,太大了 所以,我们让对10007 取模 输入输出格式 输入格式: n 输出格式:  % 10007 输入 ...

  3. 2.一个EJB的小Demo

    新建一个java普通项目即可 这里用到了Jboss,需要安装Jboss,然后进入jboss-4.2.3.GA\client目录,拷贝所有的jar包到本项目的lib下. 3个接口分别如下所示: publ ...

  4. Hibernate第三次测试错题解析

    此题目考查的是Hibernate查询缓存适用的场合,对于经常使用的查询语句, 如果启用了查询缓存,当第一次执行查询语句时,Hibernate会把查询结果存放在第二缓存中. 以后再次执行该查询语句时,只 ...

  5. Linux内核模块开发基础【转】

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51298180 1. 什么是内核模块 内核模块具有以下两个特点:1. 模块本身并不 ...

  6. Linux下Nginx+Tomcat负载均衡和动静分离配置要点

    本文使用的Linux发行版:CentOS6.7 下载地址:https://wiki.centos.org/Download 一.安装Nginx 下载源:wget http://nginx.org/pa ...

  7. 初学C#和MVC的一些心得,弯路,总结,还有教训(4)--Cache 关于创建多个缓存实例

    asp.net中的数据缓存可以用 HttpRuntime.Cache ,这个是大家都知道的,但如果缓存的数据比较多,又比较杂乱,想要把缓存分开管理(也就是创建多个缓存实例)应该怎么做呢... 于是常规 ...

  8. DNS简析

    IntroductionName Server架构分层管理机制分层查询机制Name Server之间的Master-Slave架构DDNS底层协议配置文件/etc/hosts/etc/resov.co ...

  9. 年底发福利了——分享一下我的.NET软件开发资源

    最近建了一个.NET软件开发资源的360网盘共享群,把收集的一些.NET软件开发资源分享给大家,也欢迎大家把好的东东分享一下. 资源主要有:开发工具.控件资源.书籍教程.网页设计.源码资源几大类,也希 ...

  10. 疑难杂症——EF+Automapper引发的查询效率问题解析

    前言:前面总结了一些WebApi里面常见问题的解决方案,本来打算来分享下oData+WebApi的使用方式的,奈何被工作所困,只能将此往后推了.今天先来看看EF和AutoMapper联合使用的一个问题 ...