mocha
在第一小結(jié)中的測試中用到了 mocha 框架,這一節(jié)就說說 mocha 框架吧。下面整理的內(nèi)容主要來源于官網(wǎng),如需了解更多請移步mocha 官網(wǎng)。
mocha 是一個功能豐富的前端測試框架,mocha 既可以基于 Node.js 環(huán)境運行也可以在瀏覽器環(huán)境運行項目地址。
Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.
安裝
安裝有兩種方式:1. 全局安裝;2. 將 mocha 作為項目依賴模塊安裝。
npm install --global mocha
npm install --save-dev mocha
起步
測試腳本的寫法
Mocha 的作用是運行測試腳本,首先必須學會寫測試腳本。所謂"測試腳本",就是用來測試源碼的腳本。下面是一個加法模塊 add.js 的代碼。
// add.js
function add(x, y) {
return x + y;
}
module.exports = add;
要測試這個加法模塊是否正確,就要寫測試腳本。通常,測試腳本與所要測試的源碼腳本同名,但是后綴名為.test.js(表示測試)或者.spec.js(表示規(guī)格)。比如,add.js 的測試腳本名字就是 add.test.js。
// add.test.js
var add = require('./add.js');
var expect = require('chai').expect;
describe('加法函數(shù)的測試', function() {
it('1 加 1 應該等于 2', function() {
expect(add(1, 1)).to.be.equal(2);
});
});
上面這段代碼,就是測試腳本,它可以獨立執(zhí)行。測試腳本里面應該包括一個或多個 describe 塊,每個 describe 塊應該包括一個或多個 it 塊。
describe 塊稱為"測試套件"(test suite),表示一組相關(guān)的測試。它是一個函數(shù),第一個參數(shù)是測試套件的名稱("加法函數(shù)的測試"),第二個參數(shù)是一個實際執(zhí)行的函數(shù)。
it 塊稱為"測試用例"(test case),表示一個單獨的測試,是測試的最小單位。它也是一個函數(shù),第一個參數(shù)是測試用例的名稱("1 加 1 應該等于 2"),第二個參數(shù)是一個實際執(zhí)行的函數(shù)。
斷言庫
上面的測試腳本里面,有一句斷言。
expect(add(1, 1)).to.be.equal(2);
所謂"斷言",就是判斷源碼的實際執(zhí)行結(jié)果與預期結(jié)果是否一致,如果不一致就拋出一個錯誤。上面這句斷言的意思是,調(diào)用 add(1, 1),結(jié)果應該等于 2。所有的測試用例(it 塊)都應該含有一句或多句的斷言。它是編寫測試用例的關(guān)鍵。斷言功能由斷言庫來實現(xiàn),Mocha 本身不帶斷言庫,所以必須先引入斷言庫。
var expect = require('chai').expect;
斷言庫有很多種,Mocha 并不限制使用哪一種,它允許你使用你想要的任何斷言庫。上面代碼引入的斷言庫是 chai,并且指定使用它的 expect 斷言風格。下面這些常見的斷言庫:
- assert 這個是 Node.js 中的斷言模塊。
- should.js
- expect.js
- chaijs
- better-assert
- unexpected.js
expect 斷言的優(yōu)點是很接近自然語言,下面是一些例子。
// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });
// 布爾值為true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;
// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);
// include
expect([1, 2, 3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;
// match
expect('foobar').to.match(/^foo/);
基本上,expect 斷言的寫法都是一樣的。頭部是 expect 方法,尾部是斷言方法,比如 equal、a/an、ok、match 等。兩者之間使用 to 或 to.be 連接。如果 expect 斷言不成立,就會拋出一個錯誤。事實上,只要不拋出錯誤,測試用例就算通過。
it('1 加 1 應該等于 2', function() {});
上面的這個測試用例,內(nèi)部沒有任何代碼,由于沒有拋出了錯誤,所以還是會通過。
在命令行中使用 mocha
在命令行使用 mocha 則需要在全局安裝:npm install mocha -g, 可以通過一些參數(shù)來測試指定的文件、指定展示結(jié)果的風格、導出測試報告等。可使用mocha --help命令查看所有參數(shù)。
在項目中使用 mocha
初始化一個 node 項目
mkdir step1 && cd step1
npm init -y
npm install --save-dev mocha
創(chuàng)建一個文件夾 test, 并在里面創(chuàng)建一個 test.js ,寫入下面的內(nèi)容。
下面這段代碼主要是簡單測試了一下數(shù)組 [1, 2, 3] 的 indexOf() 方法。預期[1, 2, 3].indexOf(4)返回-1。
var assert = require('assert');
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal([1, 2, 3].indexOf(4), -1);
});
});
});
修改 package.json 文件。
{
"scripts": {
"test": "mocha"
}
}
然后打開終端,在命令行中運行npm run test, 下面是運行結(jié)果。

mocha 基礎(chǔ)用法
測試回調(diào)方法被多次調(diào)用
如果使用基于回調(diào)的異步測試,如果 done()被多次調(diào)用,則 Mocha 將拋出錯誤。這對于捕捉意外的雙重回調(diào)很方便。
it('double done', function(done) {
// Calling `done()` twice is an error
setImmediate(done);
setImmediate(done);
});
回調(diào)多次意外運行錯誤結(jié)果

異步代碼檢查
使用 Mocha 測試異步代碼非常簡單!只需在測試完成時調(diào)用回調(diào)。通過向它添加一個回調(diào)函數(shù)(通常名為 done),Mocha 會知道它應該等待這個函數(shù)被調(diào)用來完成測試。該回調(diào)接受 Error 實例(或其子類)或偽造值;其他任何事情都會導致測試失敗。
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(function(err) {
if (err) done(err);
else done();
});
});
});
});
為了使事情更簡單,done()回調(diào)函數(shù)也接受一個 Error 實例(即新的 Error()),所以我們可以直接使用它:
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(done);
});
});
});
運行 Promise
或者,您可以不使用 done()回調(diào),而是返回一個 Promise。如果您正在測試的 API 返回 Promise 而不是回調(diào),這很有用:
beforeEach(function() {
return db.clear().then(function() {
return db.save([tobi, loki, jane]);
});
});
describe('#find()', function() {
it('respond with matching records', function() {
return db.find({ type: 'User' }).should.eventually.have.length(3);
});
});
使用 async/await
如果您的 JS 環(huán)境支持異步/等待,您也可以編寫像這樣的異步測試:
beforeEach(async function() {
await db.clear();
await db.save([tobi, loki, jane]);
});
describe('#find()', function() {
it('responds with matching records', async function() {
const users = await db.find({ type: 'User' });
users.should.have.length(3);
});
});
測試同步的代碼
在測試同步代碼時,省略回調(diào),mocha 將自動繼續(xù)下一次測試。
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
[1, 2, 3].indexOf(5).should.equal(-1);
[1, 2, 3].indexOf(0).should.equal(-1);
});
});
});
箭頭函數(shù)
不鼓勵在 mocha 中使用箭頭函數(shù)。 表達式中綁定的 this,不能訪問 mocha 上下文。例如,以下代碼將失?。?/p>
describe('my suite', () => {
it('my test', () => {
// should set the timeout of this test to 1000 ms; instead will fail
this.timeout(1000);
assert.ok(true);
});
});
如果你不需要使用 mocha 的上下文,表達式是可以正常運行的。但是,如果最終需要重構(gòu),結(jié)果可能會與預期有所不同。
鉤子
憑借其默認的“BDD”風格界面,Mocha 提供了 before(),after(),beforeEach()和 afterEach()之前的鉤子。這些應該用于設(shè)置先決條件并在測試后進行清理。
describe('hooks', function() {
before(function() {
// runs before all tests in this block
});
after(function() {
// runs after all tests in this block
});
beforeEach(function() {
// runs before each test in this block
});
afterEach(function() {
// runs after each test in this block
});
// test cases
});
測試可以在你的鉤子之前,之后或穿插出現(xiàn)。視情況而定,掛鉤將按其定義的順序運行;所有 before() 鉤子運行(一次),然后任何 beforeEach() 鉤子,測試,任何 afterEach() 鉤子,以及最后的 after() 鉤子(一次)。
鉤子描述
任何鉤子都可以通過可選的描述來調(diào)用,從而更容易查明測試中的錯誤。如果鉤子被賦予了一個命名函數(shù),那么如果沒有提供描述,將使用該名稱。
beforeEach(function() {
// beforeEach hook
});
beforeEach(function namedFun() {
// beforeEach:namedFun
});
beforeEach('some description', function() {
// beforeEach:some description
});
異步鉤子
所有鉤子(before(), after(), beforeEach(), afterEach())都可能是同步或異步的,其行為與常規(guī)測試案例非常相似。例如下面的代碼,您可能希望在每次測試之前用虛擬內(nèi)容填充數(shù)據(jù)庫:
describe('Connection', function() {
var db = new Connection(),
tobi = new User('tobi'),
loki = new User('loki'),
jane = new User('jane');
// 每次測試前填充數(shù)據(jù)
beforeEach(function(done) {
db.clear(function(err) {
if (err) return done(err);
db.save([tobi, loki, jane], done);
});
});
describe('#find()', function() {
it('respond with matching records', function(done) {
db.find({ type: 'User' }, function(err, res) {
if (err) return done(err);
res.should.have.length(3);
done();
});
});
});
});
root 級別的鉤子
你也可以選擇任何文件并添加“root”級別的鉤子。例如,在所有 describe()塊之外添加 beforeEach()。這里定義的回調(diào) beforeEach()在任何測試用例之前運行,而不管它存在于哪個文件中(這是因為 Mocha 有一個隱含的 describe()塊,稱為“root 套件”)。
beforeEach(function() {
console.log('before every test in every file');
});
延遲執(zhí)行 root 套件
如果您需要在運行任何套件之前執(zhí)行異步操作,則可能會延遲 root 套件。用--delay 標志運行 mocha。這將在全局上下文中附加一個特殊的回調(diào)函數(shù) run():
setTimeout(function() {
// do some setup
describe('my suite', function() {
// ...
});
run();
}, 5000);
待定的測試用例
待定的測試用例就是指的那些最終需要完成而待完成測試的用例,這些實例只有描述而沒有會回調(diào)。待測試將包含在測試結(jié)果中,并標記為待處理。未決測試不被視為失敗測試。如下面這種:
describe('Array', function() {
describe('#indexOf()', function() {
// pending test below
it('should return -1 when the value is not present');
});
});
運行結(jié)果

測試用例管理
當有很多測試用例。有時,我們希望只運行其中的幾個,這時可以用 only 方法。describe 塊和 it 塊都允許調(diào)用 only 方法,表示只運行某個測試套件或測試用例。
describe('Array', function() {
describe.only('#indexOf()', function() {
// ...
});
});
describe('Array', function() {
describe('#indexOf()', function() {
it.only('should return -1 unless present', function() {
// ...
});
it('should return the index when present', function() {
// ...
});
});
});
有時需要跳過一些測試用例可以使 skip 方法:
describe('Array', function() {
describe.skip('#indexOf()', function() {
// ...
});
});
describe('Array', function() {
describe('#indexOf()', function() {
it.skip('should return -1 unless present', function() {
// this test will not be run
});
it('should return the index when present', function() {
// this test will be run
});
});
});
有時需要根據(jù)環(huán)境來判斷是否跳過或者指定運行一些實例,可以參考下面的代碼
it('should only test in the correct environment', function() {
if (/* check test environment */) {
// make assertions
} else {
this.skip();
}
});
it('should only test in the correct environment', function() {
if (/* check test environment */) {
// make assertions
} else {
// do nothing
}
});
before(function() {
if (/* check test environment */) {
// setup code
} else {
this.skip();
}
});