11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【angularJS × Jasmine】$timeout を spyOn する方法

Posted at

はじめに

$timeout

$timeout は window.setTimeout をラップした angularJS のサービスで、次のように使う

it "$timeout は予約をキャンセルすることが出来る", ->
  inject ["$timeout", ($timeout) ->
    counter = 0
    promise = $timeout (-> counter++), 1000 # 1 秒後にカウントアップ
    $timeout.cancel promise # だが断る
    $timeout.flush
    expect(counter).toBe 0
]

spyOn

spyOn はテストダブルのための jasmine の機能で、その関数が呼ばれたかどうかをチェックする

it "spyOn は関数が呼ばれたかどうかをテスト出来る", ->
  hoge =
    fuga: -> "fuga"
    foo: -> fuga()
  spyOn(hoge, "fuga").and.callThrough()
  hoge.foo()
  expect(hoge.fuga).toHaveBeenCalled()

$timeout を spyOn するには?

spyOn はオブジェクトの要素となっている関数は spy することが出来るが、$timeout みたいな、どこにも属していないような関数を spy することが出来ない。

失敗: 無理やり

it "別の Object の要素にした上でそいつを spy", ->
  inject ["$timeout", ($timeout) ->
    hoge =
      $timeout: $timeout
    spyOn(hoge, "$timeout").and.callThrough()
    $timeout (-> ), 1000
    expect(hoge.$timeout).not.toHaveBeenCalled()
    expect(hoge.$timeout).not.toBe $timeout
  ]

→ spyOn した時点で、 $timeouthoge.$timeout は別物

準成功: decorator + jasmine.createSpy を使用する

beforeEach ->
  module ["$provide", ($provide) ->
    $provide.decorator "$timeout", ($delegate) ->
      return jasmine.createSpy $delegate
  ]
it "flush とか cancel が居なくなる", ->
  inject ["$timeout", ($timeout) ->
    counter = 0
    $timeout (-> counter ++), 1000
    expect($timeout).toHaveBeenCalled() # 成功
    $timeout.flush() # 失敗。 undefined is not a function
    expect(counter).toBe 1
  ]

jasmine.createSpy を呼ぶと、次のような関数が返る。

console.log($timeout.toString())
function () {
  callTracker.track({
    object: this,
    args: Array.prototype.slice.apply(arguments)
  });
  return spyStrategy.exec.apply(this, arguments);
}

要は、$timeout は関数であって、 flush や cancel が使えなくなってしまう。

失敗: decorator + createSpyObj

createSpyObj なら、関数の代わりに Obj を生成してくれる。

beforeEach ->
  module ["$provide", ($provide) ->
    $provide.decorator "$timeout", ($delegate) ->
      return jasmine.createSpyObj $delegate, Object.keys $delegate # 第2引数で渡した配列の各要素がオブジェクトの key になる
  ]
it "$timeout 自体は spy ではない"
  inject ["$timeout", ($timeout) ->
    $timeout (-> ), 1000
    expect($timeout).toHaveBeenCalled() # Expected a spy, but got { うんたらかんたら
  ]

$timeout の各キーが spy になる模様

成功 decorator + 無理やり

beforeEach ->
  module ["$provide", ($provide) ->
    $provide.decorator "$timeout", ($delegate) ->
      holder =
        $timeout: $delegate
      spyOn(holder, "$timeout").and.callThrough()
      return holder.$timeout
  ]
it "スパッてる"
  inject ["$timeout", ($timeout) ->
    $timeout (-> ), 1000
    expect($timeout).toHaveBeenCalled() # 成功
  ]

おまけ

需要ありそうなので helper 化した

spyDecorator = ($delegate) ->
  holder = {$delegate}
  spyOn(holder, "$delegate").and.callThrough()
  return holder.$delegate
11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?