原文檔地址:https://www.chaijs.com/api/bdd/
BDD風格包含expect和should。它們以相同的鏈式結構進行斷言,它們的不同之處在於初始的斷言構造。可以查看風格指南來進行比較。
譯註:風格指南中相關的翻譯如下:
(鑑於有代碼所以沒有放入引用格式而是用分割線與正文區分)
BDD風格有兩種風格:expect和should。兩者都使用相同的可鏈接語言來構造斷言,但它們在最初構造斷言的方式上有所不同。在使用should的情況下將可能會產生一些問題,這也有一些方式去克服這些問題。
Expect: BDD風格暴露expect或should接口。在這兩種情況下,你可以用自然語言的形式來鏈接斷言。 Expect也允許你在任何可能發生的斷言失敗之前添加任意信息。當與布爾值、數字等非描述性主題一起連用時,這將十分好用。
Should: Should允許你使用與Expect接口相同的鏈式斷言風格,然而當should風格啓動鏈式斷言時將可能在IE瀏覽器下存在一些問題,因此要注意瀏覽器兼容性。
兩者間的區別:
首先要注意的是,expect的引入僅需要引入expect函數,而should的引入需要引入should的函數執行。
var chai = require('chai')
, expect = chai.expect
, should = chai.should();
expect接口提供了一個函數作爲鏈接語言斷言的起點。它適用於node.js和所有瀏覽器。
should接口實例化對象原型的產生單一實例來進行斷言。它適用於node.js以及除Internet Explorer之外的所有現代瀏覽器。
Should需要額外注意的:
should通過實例化對象的原型進行工作,所以在一些情況下他將可能不會工作
API參照:
鏈式語言結構
使用以下的鏈式供給(getter)來提高你斷言的可閱讀性啊。
譯註:以下的鏈式結構語句單純是爲了提高閱讀性所加的一些自然語句。實際上是沒有作用的。後續的api纔是斷言所用的接口。
Chains
- to
- be
- been
- is
- that
- which
- and
- has
- have
- with
- at
- of
- same
- but
- does
- still
.not
否定在其之後鏈接的所有斷言。
expect(function () {}).to.not.throw();
expect({a: 1}).to.not.have.property('b');
expect([1, 2]).to.be.an('array').that.does.not.include(3);
這僅意味這你可以使用.not來對斷言進行否定,而不意味着你應該這樣去做。能力越大責任就越大。通常最好的做法是產生一個期待輸出的斷言,而不是產生無數個不會發生的非期待斷言之一。
expect(2).to.equal(2); // 推薦的方式
expect(2).to.not.equal(1); // 不推薦的方式
.deep
將.equal, .include, .members, .keys 和 .property放在.deep鏈式之後將導致使用深度相等的方式來代替嚴格相等(===)
// 目標對象深度(但不嚴格) 等於 `{a: 1}`
expect({a: 1}).to.deep.equal({a: 1});
expect({a: 1}).to.not.equal({a: 1});
// 目標數組深度(但不嚴格) 包含 `{a: 1}`
expect([{a: 1}]).to.deep.include({a: 1});
expect([{a: 1}]).to.not.include({a: 1});
// 目標對象深度(但不嚴格) 包含 `x: {a: 1}`
expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
expect({x: {a: 1}}).to.not.include({x: {a: 1}});
// 目標對象深度(但不嚴格) 含有成員 `{a: 1}`
expect([{a: 1}]).to.have.deep.members([{a: 1}]);
expect([{a: 1}]).to.not.have.members([{a: 1}]);
// 目標Set集深度(但不嚴格) 擁有鍵 `{a: 1}`
expect(new Set([{a: 1}])).to.have.deep.keys([{a: 1}]);
expect(new Set([{a: 1}])).to.not.have.keys([{a: 1}]);
// 目標對象深度(但不嚴格) 擁有屬性 `x: {a: 1}`
expect({x: {a: 1}}).to.have.deep.property('x', {a: 1});
expect({x: {a: 1}}).to.not.have.property('x', {a: 1});
.nested
在其後使用的.property 和 .include斷言中可以使用.和括號表示法。
expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]');
expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'});
如果. 或者 []是屬性名的一部分,可以在它們之前添加雙反斜槓進行轉義
expect({'.a': {'[b]': 'x'}}).to.have.nested.property('\\.a.\\[b\\]');
expect({'.a': {'[b]': 'x'}}).to.nested.include({'\\.a.\\[b\\]': 'x'});
.nested不可與.own斷言連用
.own
使得其後的.property 和 .include斷言中的繼承屬性被忽略。
Object.prototype.b = 2;
expect({a: 1}).to.have.own.property('a');
expect({a: 1}).to.have.property('b');
expect({a: 1}).to.not.have.own.property('b');
expect({a: 1}).to.own.include({a: 1});
expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2});
.own不可與.nested斷言連用
.ordered
使得其後的.members斷言需求以相同(譯註:不加ordered時member斷言值斷言成員存在性而忽視順序)的順序斷言其成員
expect([1, 2]).to.have.ordered.members([1, 2])
.but.not.have.ordered.members([2, 1]);
當共同使用.include和.orderd時,排序開始於數組的首部。
expect([1, 2]).to.have.ordered.members([1, 2])
.but.not.have.ordered.members([2, 1]);
.any
使得跟在其後的.key斷言僅需求目標包含至少一個所給定的鍵名,它與需求目標滿足所有所給鍵的.all斷言是相反的。
expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd');
.all
使得跟在其後的.key斷言僅需求目標需要包含所有所給的鍵名,它與僅需求目標包含至少一個所給定的鍵名.any斷言是相反的。
expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
需要注意的是當all和any都沒有在鏈式之前添加的時候是默認使用all斷言的,然而最好的方式通常是顯式的加上all以提高斷言的可讀性。
.lengthOf(n[, msg]):
斷言目標的length或size與給定值相同,接受參數msg在斷言錯誤時給出提示。其也可以與.above、.below、.most、.lease、.within等斷言連用。由於兼容性問題,lengthOf的別名length不能直接鏈接到未經經聲明的方法如.a,,因此兩者不可互換,應該用lengthOf來代替length。
.lengthOf接受當斷言失敗時的客戶錯誤提示參數msg
。
該參數也可以作爲expect的第二個參數給出。
expect([1, 2, 3]).to.have.lengthOf(2, 'nooo why fail??');
expect([1, 2, 3], 'nooo why fail??').to.have.lengthOf(2);
譯註:當api中含有可選參數[msg]時均指當斷言失敗時的客戶錯誤提示參數
msg
。此時當該斷言出現失敗時用戶提示信息可以用以下兩種形式給出。
expect(target).somechain.somechain.(val, [msg]);
expect(target, [msg]).somechain.somechain.(val);
後續api中會多次出現[msg]均爲可在此處傳入錯誤信息的含義。如果該api參數中不包含[msg]參數,則說明其僅接受錯誤信息參數作爲expect的第二個參數被給出。
.match(re[, msg]):
斷言目標與給定的正則規則相匹配。
.string(str[, msg]):
斷言目標字符串包含所給子串,支持message在出錯時給出用戶信息。
.keys(key1[, key2[, …]]):
斷言目標對象、數組、map集、set集包含所給鍵,只有目標的自身屬性在所搜索範圍內。當目標是一個對象或者一個數組時,鍵可以以一個或者多個字符串參數的形式提供,也可以是一個單一的數組參數或這一個單獨的對象參數,當是後一種情況(單獨的對象參數)時,僅考慮給定對象的鍵素材,而忽略它的值。當目標是一個set或者map時,每一個鍵都必須以參數分隔的形式提供。因爲.key基於不同目標類型做了不同的事,所以在使用.key之前進行類型檢測是非常重要的。
默認情況下,使用嚴格相等的方式比較set和map鍵,在鏈式中較早添加.deep可以使用深度相等的方式去替換它。
默認情況下目標必須含有所有給出的鍵,可以在鏈式之前加入.any使得目標僅需包含至少一個給出的鍵。需要注意的是當.all和.any都沒有添加的時候默認是使用.all的,但是推薦顯示的使用.all來提高閱讀性。
當.include和.any連用的時候,僅.any生效
// 以下的兩種斷言是不同的
expect({a: 1}).to.have.any.keys('a', 'b’);
expect({a: 1}).to.include.any.keys('a', 'b');
其錯誤信息僅可以作爲expect的第二個參數被給出。
.throw([errorLike], [errMsgMatcher], [msg])
當沒有提供參數時,.throw調用目標函數並斷言有一個錯誤將被拋出。當有一個參數被提供,並且該參數是一個錯誤構造器,.throw調用目標函數並斷言有一個錯誤被拋出,該錯誤是所給構造器的一個實例。
當僅提供一個參數,且該參數是一個錯誤實例時,.throw調用目標函數並斷言其產生一個與給定錯誤實例嚴格相等的錯誤。
當僅提供一個字符串參數時,.throw調用目標函數並斷言其產生一個包含給定字符串信息的錯誤。
當僅提供一個正則表達式參數時,.throw調用目標函數並斷言其產生一個符合所給正則表達式信息的錯誤。
注意一些常見的錯誤:
注意傳入函數由.throw進行調用,而不是自己調用
expect(fn).to.throw(); // Good! Tests `fn` as desired
expect(fn()).to.throw(); // Bad! Tests result of `fn()`, not `fn`
另一個常見問題是當目標是一個對象的方法或者是一個依賴this獨立運行的函數時,由於函數被.throw調用時會丟失原有的this環境而無法得知所謂的this到底指什麼。關於這個問題目前有兩種方案,第一種是將方法或函數在另一個函數中調用,另一種方式是使用bind進行綁定
.throws和.Throw可以與.throw互換
.respondTo(method[, msg])
當目標是一個非函數對象時, .respondTo斷言該對象包含所提供方法名稱的方法,該方法可以是自身的也可以是繼承的,可以是可枚舉也可以時不可枚舉的。
當目標是一個函數時斷言該函數原型屬性又一個與所給名稱相同的方法。同樣的,該方法可以是自身的也可以是繼承的,可以是可枚舉也可以時不可枚舉的。
.itself
強制鏈中的所有斷言表現得好像目標是非函數對象,即使它是一個函數。因此,它導致斷言目標具有給定名稱的方法,而不是斷言目標的原型屬性具有給定名稱的方法。
.satisfy(matcher[, msg])
將目標值作爲所給matcher方法的第一個參數進行調用,並斷言返回值爲真。
.closeTo(expected, delta[, msg])
斷言目標值是一個基於期待值(expected)+/- 差值(delta)範圍內的數字。
.members(set[, msg])
斷言目標數組與所給數組set具有相同成員。
默認情況下是斷言成員嚴格相等,在鏈式之前增加.deep可以使使用深度相等的方式替代嚴格相等
// Target array deeply (but not strictly) has member `{a: 1}`
expect([{a: 1}]).to.have.deep.members([{a: 1}]);
expect([{a: 1}]).to.not.have.members([{a: 1}]);
默認情況下,順序是不進行匹配的。在鏈式之前添加.ordered可以使得成員匹配按照相同的順序進行。
expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]);
expect([1, 2, 3]).to.have.members([2, 1, 3]) .but.not.ordered.members([2, 1, 3]);
默認情況下,目標數組應與所給數組集保持相同size,在鏈式之前添加.include可以使得目標數組爲期待數組的一個超集。需要注意的是在添加.include後,子集中重複的成員是會被忽略的
.deep、.include和.ordered都可以與其聯合使用。值得注意的是,當鏈式中存在.include和.ordered時,期待數組與目標數組應該保持相同的順序。
expect([{a: 1}, {b: 2}, {c: 3}]) .to.include.deep.ordered.members([{a: 1}, {b: 2}])
.but.not.include.deep.ordered.members([{b: 2}, {c: 3}]);
.oneOf(list[, msg])
斷言目標時期待數組list的成員,但最好的方式時斷言其爲某個確認的值。
將會以嚴格相等的方式進行比較。
.change(subject[, prop[, msg]])
當一個參數被給定時。.change斷言給定函數subject在目標函數之前調用和在目標函數之後調用的返回值不同。但更好的方式時斷言函數會返回具體的某個值。
當有兩個函數被提供時,.change斷言對象subject的屬性prop在目標函數調用前後的值是不同的。
.increase(subject[, prop[, msg]])
當一個參數被給定時。.increase斷言給定函數subject在目標函數之後調用比在目標函數之前調用的返回值更大。但更好的方式是斷言函數會增加具體的數值而不是斷言他會增加任意數值。
var myObj = {val: 1}
, subtractTwo = function () { myObj.val -= 2; };
expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
expect(subtractTwo).to.not.increase(myObj, 'val'); // Not recommended
當有兩個函數被提供時,.increase斷言對象subject的屬性prop在目標函數調用後比調用之前更大。
.decrease(subject[, prop[, msg]])
當一個參數被給定時。.decrease斷言給定函數subject在目標函數之後調用比在目標函數之前調用的返回值更小。但更好的方式是斷言函數會減少具體的數值而不是斷言他會減少任意數值。
var val = 1
, subtractTwo = function () { val -= 2; }
, getVal = function () { return val; };
expect(subtractTwo).to.decrease(getVal).by(2); // Recommended
expect(subtractTwo).to.decrease(getVal); // Not recommended
當有兩個函數被提供時,.increase斷言對象subject的屬性prop在目標函數調用後比調用之前更小。
.by(delta[, msg])
當.by跟在.increase鏈式斷言之後,.by斷言.increase斷言的增量爲給定的差值(delta)。
當.by跟在.decrease鏈式斷言之後,.by斷言.decrease斷言的減少量爲給定的差值(delta)。
當.by跟在.change鏈式斷言之後,.by斷言.change斷言的差量爲給定的差值(delta)。但這樣做會產生問題,因爲這將會產生一個不確定的期待,通常最好的做法是確認一個精準的期待輸出,而只用精確輸出去斷言。
var myObj = {val: 1}
, addTwo = function () { myObj.val += 2; }
, subtractTwo = function () { myObj.val -= 2; };
expect(addTwo).to.increase(myObj, 'val').by(2); // 推薦的做法
expect(addTwo).to.change(myObj, 'val').by(2); // 不推薦的做法
expect(subtractTwo).to.decrease(myObj, 'val').by(2); // 推薦的做法
expect(subtractTwo).to.change(myObj, 'val').by(2); // 不推薦的做法
.extensible
斷言目標是可擴展的,這意味着可以向其添加新屬性。原始值是絕對不可擴展的。在.extensible鏈式結構之前添加.not可以否定.extensible.
譯註:對象的可拓展性用以表示是否可以給對象添加新屬性。所有內置對象和自定義對象都是顯示可拓展的,除非將它們轉換爲不可拓展的。同樣,宿主對象的可拓展性也是由實現ECMAScript5的IavaScript引擎定義的。
var nonExtensibleObject = Object.preventExtensions({})
, sealedObject = Object.seal({})
, frozenObject = Object.freeze({});
expect(nonExtensibleObject).to.not.be.extensible;
expect(sealedObject).to.not.be.extensible;
expect(frozenObject).to.not.be.extensible;
expect(1).to.not.be.extensible;
其錯誤信息僅可以作爲expect的第二個參數被給出。
expect(1, 'nooo why fail??').to.be.extensible;
.sealed
斷言目標是密封的,這意味着將無法加入新屬性,並且已存在的屬性無法重新配置或者刪除,這也意味着已存在的屬性在重新分配不同值之後仍然可能保持。原始值均是密封的。
var sealedObject = Object.seal({});
var frozenObject = Object.freeze({});
expect(sealedObject).to.be.sealed;
expect(frozenObject).to.be.sealed;
expect(1).to.be.sealed;
在.sealed鏈式結構之前添加.not可以否定.sealed。
expect({a: 1}).to.not.be.sealed;
其錯誤信息僅可以作爲expect的第二個參數被給出。
expect({a: 1}, 'nooo why fail??').to.be.sealed;
.frozen
斷言目標是密封的,這意味着將無法加入新屬性,並且已存在的屬性無法被重新分配值,重新配置或者刪除。原始值總是凍結的。
var frozenObject = Object.freeze({});
expect(frozenObject).to.be.frozen;
expect(1).to.be.frozen;
在.frozen鏈式結構之前添加.not可以否定.frozen。
expect({a: 1}).to.not.be.frozen;
其錯誤信息僅可以作爲expect的第二個參數被給出。
expect({a: 1}, 'nooo why fail??').to.be.frozen;
.finite
斷言目標是一個非NaN的數字,且該數字既不是正無窮也是負無窮。
expect(1).to.be.finite;
可以在鏈式之前增加.not進行否定,但這樣的做法是危險的,因爲這將會產生一個不確定的期待,它可能不是一個數字,或者是NaN,也可能是正無窮或者負無窮。所以通常最好的做法是期待它產生一個精確的輸出,然後僅僅去斷言可能會產生的期待。
當目標不被期待是一個數字是最好是去斷言它是某個你期待的類型,而不是斷言它是許多個不符合斷言的類型之一。
expect('foo').to.be.a('string'); // Recommended
expect('foo').to.not.be.finite; // Not recommended
其錯誤信息僅可以作爲expect的第二個參數被給出。
expect('foo', 'nooo why fail??').to.be.finite;
.fail([message])
.fail(actual, expected, [message], [operator])
拋出一個錯誤。
expect.fail();
expect.fail("custom error message");
expect.fail(1, 2);
expect.fail(1, 2, "custom error message");
expect.fail(1, 2, "custom error message", ">");
expect.fail(1, 2, undefined, ">");
譯註:當僅有一個參數傳入時拋出一個錯誤,將所傳入信息作爲錯誤信息。當傳入三個參數時,分別 傳入實際值,期待的值和錯誤提示,並將這個錯誤拋出。