4.4 J asmine的断言
Jasmine的断言很接近于自然语言。断言的写法分为两部分:
expect.matcher
第一部分expect函数接受一个参数,这个参数代表“实际值”,也就是被测代码的运行结果。
expect需要和第二部分matcher(匹配器)一起使用。匹配器接受“期望值”,将“期望值”与“实际值”进行布尔判断。Jasmine根据匹配器的结果决定测试用例是否通过测试。以前面Calc_spec.js里的断言为例:
expect(result).toBe(4);
result是“实际值”, toBe是Jasmine内建的一个匹配器,而4则是“期望值”。这个测试用例期望result的值是4。
如果匹配器要执行否定判断,可以在expect和匹配器间加上not,例如:
expect(false).not.toBe(true);
4.4.1 内置匹配器
Jasmine提供了丰富的内置匹配器。
1.toBe
该内置匹配器的语义是期望“实际值”和“期望值”严格相等(执行JavaScript"==="运算)。例如:
it('toBe', function () { var a = {}; var b = a; var c = {}; expect(a).toBe(b); expect(a).not.toBe(c); });
如果想了解某个匹配器如何进行布尔判断,可以查阅jasmine.js文件中相应匹配器的源代码。例如toBe匹配器的代码如下:
getJasmineRequireObj().toBe = function() { function toBe() { return { compare: function(actual, expected) { return { pass: actual === expected }; } }; } return toBe; }
2.toBeCloseTo
该内置匹配器的语义是期望“实际值”和“期望值”足够接近(不一定要相等)。例如:
it('toBeCloseTo', function () {
var a = 3.78,
b = 3.76;
expect(a).not.toBeCloseTo(b, 2);
expect(a).toBeCloseTo(b, 1);
});
所谓“足够接近”,就是把两个数按照指定的精度进行四舍五入后比较是否相等。toBeCloseTo的第二个参数用于指定精度。以上第一个断言里保留两位小数,所以“不接近”,第二个断言里只保留一位小数,所以“足够接近”。
3.toBeDefined
该内置匹配器的语义是期望“实际值”已定义。例如:
it('toBeDefined', function () {
var a = {
foo: 'foo'
};
expect(a.foo).toBeDefined();
expect(a.bar).not.toBeDefined();
});
4.toBeFalsy
该内置匹配器用于实现布尔测试,使期望“实际值”为falsy。例如:
it('toBeFalsy', function () { var a, foo = 'foo'; expect(a).toBeFalsy(); expect(foo).not.toBeFalsy(); });
JavaScript里除了下列falsy值,其余所有的值都是truthy,包括“0”、“false”、空的function、空的array和空的object。
• false
• null
• undefi ned
• 0
• NaN
• ‘’(空字符串)
• “”(空字符串)
• document.all
5.toBeTruthy
该内置匹配器用于实现布尔测试,使期望“实际值”为truthy,和toBeFalsy相反。例如:
it('toBeTruthy', function () { var a, foo = 'foo'; expect(foo).toBeTruthy(); expect(a).not.toBeTruthy(); });
6.toBeGreaterThan
该内置匹配器的语义是期望“实际值”大于“期望值”。例如:
it('toBeGreaterThan', function () { var a = 3.78, b = 3.76; expect(a).toBeGreaterThan(b); expect(b).not.toBeGreaterThan(a); });
7.toBeGreaterThanOrEqual
该内置匹配器的语义是期望“实际值”大于或等于“期望值”。
8.toBeLessThanOrEqual
该内置匹配器的语义是期望“实际值”小于或等于“期望值”。
9.toBeLessThan
该内置匹配器的语义是期望“实际值”小于“期望值”。
10.toBeNaN
该内置匹配器的语义是期望“实际值”是NaN(Not-A-Number)。例如:
it('toBeNaN', function () { expect(0 / 0).toBeNaN(); expect(parseInt('foo')).toBeNaN(); expect(5).not.toBeNaN(); });
11.toBeNull
该内置匹配器的语义是期望“实际值”是null。例如:
it('toBeNull', function () { var a = null; var foo = 'foo'; expect(a).toBeNull(); expect(foo).not.toBeNull(); });
12.toBeUndefined
该内置匹配器的语义是期望“实际值”未被定义,和toBeDefined相反。例如:
it('toBeUndefined', function () { var a = { foo: 'foo' }; expect(a.foo).not.toBeUndefined(); expect(a.bar).toBeUndefined(); });
13.toContain
该内置匹配器的语义是期望“实际值”(数组或对象)包含“期望值”。例如:
describe('toContain', function () { it('finds an item in an Array', function () { var a = ['foo', 'bar', 'baz']; expect(a).toContain('bar'); expect(a).not.toContain('quux'); }); it('finds a substring', function () { var a = 'foo bar baz'; expect(a).toContain('bar'); expect(a).not.toContain('quux'); }); });
14.toEqual
该内置匹配器的语义是期望两个对象(“实际值”和“期望值”)相等。例如:
describe('toEqual', function () { it('works for simple literals and variables', function () { var a = 12; expect(a).toEqual(12); }); it('works for objects', function () { var foo = { a: 12, b: 41 }; var bar = { a: 12, b: 41 }; expect(foo).toEqual(bar); }); });
查看Jasmine里toEqual函数的实现,会注意到toEqual函数接受cutomEqualityTesters参数,这个参数是自定义相等检验器。如果用户定义了自己的相等检验器,Jasmine会使用这些相等检验器对“实际值”和“期望值”进行相等比较。例如:
function toEqual(util, customEqualityTesters) { customEqualityTesters = customEqualityTesters || []; return { compare: function(actual, expected) { … result.pass = util.equals(actual, expected, customEqualityTesters); … } }; }
15.toMatch
该内置匹配器的语义是期望“实际值”能够匹配“期望值”,“期望值”可以是正则表达式,也可以是字符串。例如:
it('toMatch is for regular expressions', function () { var message = "foo bar baz"; expect(message).toMatch(/bar/); expect(message).toMatch("bar"); expect(message).not.toMatch(/quux/) });
16.toThrow
该内置匹配器的语义是期望“实际值”(函数)会抛出异常。例如:
it('toThrow', function () { var foo = function () { return 1 + 2; }; var bar = function () { return a + 1; }; expect(foo).not.toThrow(); expect(bar).toThrow(); });
17.toThrowError
该内置匹配器的语义是期望“实际值”(函数)抛出“期望值”所指定的异常,异常信息可以是字符串、正则表达式、错误类型和错误信息。例如:
it('toThrowError is for testing a specific thrown exception', function() { var foo = function() { throw new TypeError("foo bar baz"); }; expect(foo).toThrowError("foo bar baz"); expect(foo).toThrowError(/bar/); expect(foo).toThrowError(TypeError); expect(foo).toThrowError(TypeError, "foo bar baz"); });
4.4.2 自定义匹配器(Custom Matcher)
如果内置匹配器无法满足需求的话,可以编写自定义匹配器来封装匹配规则。
Jasmine使用工厂模式创建自定义匹配器。匹配器工厂是一个函数,函数名就是自定义匹配器的名称,也就是暴露给expect调用的名称。它接受两个参数:
• util,给匹配器使用的一组工具函数。
• customEqualityTesters,调用util.equals时所必需。
匹配器工厂返回一个对象,这个对象要包含名为compare的函数,即匹配器的对比函数。这个函数将实现自定义匹配规则。Jasmine在执行匹配时会调用compare函数。以自定义匹配器isBetween为例,示例代码如下:
var customMatchers = { isBetween: function(util, customEqualityTesters){ return { compare:function(actual, min, max) { var result = { pass: false, message: 'Expected ' + actual + ' is not between ' + min + ' and ' + max }; if(actual >= min && actual <= max){ result.pass = true; result.message = 'Expected ' + actual + ' is between ' + min + ' and ' + max; ; } return result; } }; } };
isBetween是匹配器工厂,也是要实现的自定义匹配器的名称。它返回一个对象,这个对象里的compare函数接受的第一个参数是actual,代表“实际值”。后面的参数是可选参数,代表“期望值”。在上面的示例里,期望“实际值”在“期望值”min和max之间。
compare函数必须返回一个结果对象。该对象必须包含一个布尔值类型的pass成员属性,告诉Jasmine匹配结果。在上面的示例里,一旦“实际值”在“期望值”min和max之间,则将pass属性设为true。
如果匹配失败,但是在返回的result对象中包含了message成员属性的话,Jasmine会使用message的值作为错误提示。
通常在beforeEach里使用jasmine.addMatchers函数注册自定义匹配器。该函数接受一个对象参数,这个对象可以包含多个匹配器工厂(在上面的示例里customerMatchers就是这样一个对象,isBetween是其中的一个字段)。例如,使用下面的代码:
beforeEach(function() { jasmine.addMatchers(customMatchers); });
就可以在测试用例里使用isBetween自定义匹配器了。
it('against isBetween', function(){ expect(8).isBetween(4, 10); });
如果用户的自定义匹配器需要控制.not的行为(不是简单的布尔值取反),那么匹配器工厂返回的对象里除了compare函数,还需要包含另外一个函数negativeCompare。这样,Jasmine使用.not的时候会执行negativeCompare函数。
4.4.3 自定义相等检验器(Custom Equality Tester)
在Jasmine里用户可以使用toEqual内置匹配器判断两个对象是否相等。如果内置匹配器的默认规则无法满足需求的话,可以创建自定义相等检验器来使用自己的相等规则。
例如,对于以下示例里的Duck类型,用户希望两个Duck对象相等的条件是canSwim、canWalk和color相等,而canFly或其他属性不属于判断条件。这种情况就需要使用自定义相等检验器。
var Duck = function () { this.canSwim = true; this.canWalk = true; this.color = 'white'; this.canFly = false; }; Duck.prototype.swim = function(){ if(this.canSwim){ //code } }; Duck.prototype.walk = function(){ if(this.canWalk){ //code } };
自定义相等检验器是一个函数,它接受两个参数,也就是要进行比较的对象。自定义相等检验器根据相应条件对这两个对象进行比较,结果返回true或false值。如果自定义相等检验器无法进行判断,则返回undefined。以下示例里,仅对两个对象的canSwim、canWalk和color属性进行比较。
describe('Custom Equality Test', function () { var duckCustomEquality = function (first, second) { return first.canSwim == second.canSwim && first.canWalk == second.canWalk && first.color == second.color; }; … });
使用自定义相等检验器前需要注册。注册自定义相等检验器通常通过在beforeEach函数里调用jasmine.addCustomEqualityTester函数实现。示例代码如下:
beforeEach(function () { jasmine.addCustomEqualityTester(duckCustomEquality); });
注册后就可以比较两个Duck对象了:
it('against Duck class', function () { var d = { canSwim: true, canWalk: true, canClimb: true, color: 'white', canFly: true }; var duck = new Duck(); expect(d).toEqual(duck); d.canSwim = false; expect(d).not.toEqual(duck); });
4.4.4 非对称相等检验器(Asymmetric Equality Tester)
自定义相等检验器用来检验两个对象是否相等。如果只需要检验某一个对象是否满足特定条件,而不是两个对象严格相等时,我们可以自定义一个非对称相等检验器。它作为一个“期望值”对象出现,必须包含一个名为asymmetricMatch的方法。Jasmine在使用toEqual比较“实际值”和非对称相等检验器时会调用这个asymmetricMatch方法。
describe('Asymmetry Match', function() {
var tester = {
asymmetricMatch: function(actual) {
var secondValue = actual.split(', ')[1];
return secondValue === 'bar';
}
};
it('dives in deep', function() {
expect('foo, bar, baz, quux').toEqual(tester);
});
});
以上示例创建了非对称相等检验器tester,它的asymmetricMatch方法期望输入的“实际值”字符串以逗号分隔后第二个内容为bar。
4.4.5 辅助匹配函数
针对一些常用情况,Jasmine提供辅助函数帮助开发人员进行匹配。这些辅助函数会返回非对称相等检验器(具有asymmetricMatch方法),封装了需要匹配的“期望值”和特殊匹配规则。
1.jasmine.any
通常传给匹配器的“期望值”是一个数字、字符串或对象等具体实例,然后将之和“实际值”做比较。有时候用户希望判断“实际值”是否是某种类型,而不是具体实例,这时可以用jasmine.any封装匹配类型,例如:
describe('jasmine.any', function () {
it('matches any value', function () {
expect({}).toEqual(jasmine.any(Object));
expect(12).toEqual(jasmine.any(Number));
});
});
jasmine.any函数接受构造函数或类名作为参数。这个构造函数或类名被jasmine.any封装成“期望值”,期望“实际值”所具备的类型。以上示例里“实际值”{}是一个Object,12是一个Number,所以测试通过。
2.jasmine.anything
jasmine.anything代表任何存在的值。只要“实际值”不是null或undefined,测试就通过。例如:
describe('jasmine.anything', function () {
it('matches anything', function () {
expect(1).toEqual(jasmine.anything());
});
});
3.jasmine.objectContaining
jasmine.objectContaining用来判断“实际值”对象是否包含某个键值对。例如:
describe('jasmine.objectContaining', function () { var foo; beforeEach(function () { foo = { a: 1, b: 2, bar: 'baz' }; }); it('matches objects with the expect key/value pairs', function () { expect(foo).toEqual(jasmine.objectContaining({ bar: 'baz' })); expect(foo).not.toEqual(jasmine.objectContaining({ c: 37 })); }); });
4.jasmine.arrayContaining
jasmine.arrayContaining用来判断输入的“期望值”是否是“实际值”数组的一部分。例如:
describe('jasmine.arrayContaining', function () { var foo; beforeEach(function () { foo = [1, 2, 3, 4]; }); it('matches arrays with some of the values', function () { expect(foo).toEqual(jasmine.arrayContaining([3, 1])); expect(foo).not.toEqual(jasmine.arrayContaining([6])); }); });
5.jasmine.stringMatching
jasmine.stringMatching用来判断输入的“期望值”是否是“实际值”字符串的一部分。如果“期望值”是正则表达式,那么就会判断“实际值”是否满足正则表达式。它也可以用来判断对象内的字符串。示例代码如下:
describe('jasmine.stringMatching', function () { it('matches as a regexp', function () { expect({ foo: 'bar' }).toEqual({ foo: jasmine.stringMatching(/^bar$/) }); expect({ foo: 'foobarbaz' }).toEqual({ foo: jasmine.stringMatching('bar') }); }); });