Javascript 测试框架 Jasmine(五):Spies
2014-06-21 · 1,307 chars · 7 min read
Spies 是 Jasmine 里面比较高端的方法,可以用来模拟函数的执行,以达到隔离复杂依赖的效果。例如,你要测试列表的处理是否正确,但是数据是异步请求接口取得的,这时你就可以使用 Spies,模拟返回各种不同的数据进行测试。
spy 可以保存任何函数的调用记录和输入的参数,spy 只能存在于 describe 和 it 中,在 spec 执行完之后销毁。先说两个针对 spies 的 Matcher,toHaveBeenCalled
用来测试函数是否被调用过;toHaveBeenCalledWith
用来测试函数被调用时的参数列表,匹配的话返回true
,看个例子:
describe('A spy', function () { var foo, bar = null beforeEach(function () { foo = { setBar: function (value) { bar = value }, } spyOn(foo, 'setBar') foo.setBar(123) foo.setBar(456, 'another param') }) it('tracks that the spy was called', function () { expect(foo.setBar).toHaveBeenCalled() }) it('tracks all the arguments of its calls', function () { expect(foo.setBar).toHaveBeenCalledWith(123) expect(foo.setBar).toHaveBeenCalledWith(456, 'another param') }) it('stops all execution on a function', function () { expect(bar).toBeNull() }) })
and.callThrough
#
spy 链式调用and.callThrough
后,在获取 spy 的同时,调用实际的函数,看示例:
describe('A spy, when configured to call through', function () { var foo, bar, fetchedBar beforeEach(function () { foo = { setBar: function (value) { bar = value }, getBar: function () { return bar }, } spyOn(foo, 'getBar').and.callThrough() foo.setBar(123) fetchedBar = foo.getBar() }) it('tracks that the spy was called', function () { expect(foo.getBar).toHaveBeenCalled() }) it('should not effect other functions', function () { expect(bar).toEqual(123) }) it('when called returns the requested value', function () { expect(fetchedBar).toEqual(123) }) })
如果不加and.callThrough
的话,最后一个测试中的fetchedBar
,其实只是spyOn
方法的返回值,并不会执行原始的函数,返回 123。
and.returnValue
#
spy 链式调用and.returnValue
,返回指定值。
describe('A spy, when configured to fake a return value', function () { var foo, bar, fetchedBar beforeEach(function () { foo = { setBar: function (value) { bar = value }, getBar: function () { return bar }, } spyOn(foo, 'getBar').and.returnValue(745) foo.setBar(123) fetchedBar = foo.getBar() }) it('tracks that the spy was called', function () { expect(foo.getBar).toHaveBeenCalled() }) it('should not effect other functions', function () { expect(bar).toEqual(123) }) it('when called returns the requested value', function () { expect(fetchedBar).toEqual(745) }) })
and.callFake
#
spy 链式调用and.callFake
,执行指定函数。
describe('A spy, when configured with an alternate implementation', function () { var foo, bar, fetchedBar beforeEach(function () { foo = { setBar: function (value) { bar = value }, getBar: function () { return bar }, } spyOn(foo, 'getBar').and.callFake(function () { return 1001 }) foo.setBar(123) fetchedBar = foo.getBar() }) it('tracks that the spy was called', function () { expect(foo.getBar).toHaveBeenCalled() }) it('should not effect other functions', function () { expect(bar).toEqual(123) }) it('when called returns the requested value', function () { expect(fetchedBar).toEqual(1001) }) })
and.throwError
#
spy 链式调用and.throwError
,抛出异常:
describe('A spy, when configured to throw an error', function () { var foo, bar beforeEach(function () { foo = { setBar: function (value) { bar = value }, } spyOn(foo, 'setBar').and.throwError('quux') }) it('throws the value', function () { expect(function () { foo.setBar(123) }).toThrowError('quux') }) })
and.stub
#
spy 恢复到原始状态,不执行任何操作。直接看下源码比较好懂 https://github.com/pivotal/jasmine/blob/master/src/core/SpyStrategy.js
describe('A spy', function () { var foo, bar = null beforeEach(function () { foo = { setBar: function (value) { bar = value }, } spyOn(foo, 'setBar').and.callThrough() }) it('can call through and then stub in the same spec', function () { foo.setBar(123) expect(bar).toEqual(123) foo.setBar.and.stub() bar = null foo.setBar(123) expect(bar).toBe(null) }) })
jasmine.createSpy
#
当没有函数可以 spy 的时候,可以创建一个“基础的”spy,它的背后没有任何实际的操作,例如:
describe('A spy, when created manually', function () { var whatAmI beforeEach(function () { whatAmI = jasmine.createSpy('whatAmI') whatAmI('I', 'am', 'a', 'spy') }) it('is named, which helps in error reporting', function () { expect(whatAmI.and.identity()).toEqual('whatAmI') }) it('tracks that the spy was called', function () { expect(whatAmI).toHaveBeenCalled() }) it('tracks its number of calls', function () { expect(whatAmI.calls.count()).toEqual(1) }) it('tracks all the arguments of its calls', function () { expect(whatAmI).toHaveBeenCalledWith('I', 'am', 'a', 'spy') }) it('allows access to the most recent call', function () { expect(whatAmI.calls.mostRecent().args[0]).toEqual('I') }) })
jasmine.createSpyObj
#
使用jasmine.createSpyObj
可以模拟多个 spies。给jasmine.createSpyObj
传入一个字符串数组,返回一个对象,字符串数组的每一项都是它的 spy 类型的属性。
describe('Multiple spies, when created manually', function () { var tape beforeEach(function () { tape = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']) tape.play() tape.pause() tape.rewind(0) }) it('creates spies for each requested function', function () { expect(tape.play).toBeDefined() expect(tape.pause).toBeDefined() expect(tape.stop).toBeDefined() expect(tape.rewind).toBeDefined() }) it('tracks that the spies were called', function () { expect(tape.play).toHaveBeenCalled() expect(tape.pause).toHaveBeenCalled() expect(tape.rewind).toHaveBeenCalled() expect(tape.stop).not.toHaveBeenCalled() }) it('tracks all the arguments of its calls', function () { expect(tape.rewind).toHaveBeenCalledWith(0) }) })
其他跟踪属性#
spy 的任何调用都可以使用calls
属性跟踪
.calls.any()
#
如果 spy 没有被调用过,则返回 false,否则返回 true
.calls.count()
#
返回 spy 被调用的次数
.calls.argsFor(index)
#
返回 spy 被第 index 次调用时的参数
.calls.allArgs()
#
返回 spy 所有被调用的参数
.calls.all()
#
返回 spy 所有被调用的this
上下文和参数
.calls.mostRecent()
#
返回 spy 最近一次被调用的this
上下文和参数
.calls.first()
#
返回 spy 第一次被调用的this
上下文和参数
.calls.reset()
#
清空 spy 的所有跟踪,看看他们的用法,点这里看在线运行效果:
describe('Jasmine Spy的跟踪属性', function () { var foo, bar = null beforeEach(function () { foo = { setBar: function (value) { bar = value }, } spyOn(foo, 'setBar') }) it('.calls.any()示例', function () { expect(foo.setBar.calls.any()).toEqual(false) foo.setBar() expect(foo.setBar.calls.any()).toEqual(true) }) it('.calls.count()示例', function () { expect(foo.setBar.calls.count()).toEqual(0) foo.setBar() foo.setBar() expect(foo.setBar.calls.count()).toEqual(2) }) it('.calls.argsFor(index)示例', function () { foo.setBar(123) foo.setBar(456, 'baz') expect(foo.setBar.calls.argsFor(0)).toEqual([123]) expect(foo.setBar.calls.argsFor(1)).toEqual([456, 'baz']) }) it('.calls.allArgs()示例', function () { foo.setBar(123) foo.setBar(456, 'baz') expect(foo.setBar.calls.allArgs()).toEqual([[123], [456, 'baz']]) }) it('.calls.all()示例', function () { foo.setBar(123) expect(foo.setBar.calls.all()).toEqual([ { object: foo, args: [123], }, ]) }) it('.calls.mostRecent()示例', function () { foo.setBar(123) foo.setBar(456, 'baz') expect(foo.setBar.calls.mostRecent()).toEqual({ object: foo, args: [456, 'baz'], }) }) it('.calls.first()示例', function () { foo.setBar(123) foo.setBar(456, 'baz') expect(foo.setBar.calls.first()).toEqual({ object: foo, args: [123], }) }) it('.calls.reset()示例', function () { foo.setBar(123) foo.setBar(456, 'baz') expect(foo.setBar.calls.any()).toBe(true) foo.setBar.calls.reset() expect(foo.setBar.calls.any()).toBe(false) }) })
– 下一篇讲一下异步代码测试 –