Javascript测试框架Jasmine(五):Spies

2014-06-27 · 22 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)
  })
})

下一篇讲一下异步代码测试 –