文章

To mock or not to mock

在寫 unit test 時會用到 mock/stub/spy 等技巧去代替一些 external dependencies,但有時要 mock 到甚麼程度確實叫人難以抉擇。簡單如這例:

 #this === window in coffeescript iife
 this.DetailTrigger = 
      init: (trigger, target)-> 
           @trigger = trigger
           @target = target
           @bindClick()
      bindClick:->
           @trigger.on 'click', =>
                @target.toggle()

這個 object 基本上只是接收兩個 jQuery object 再為他們建立 binding,僅此而已。通常的測試法,就是設定 fixtures,用 jasmine(配 jasmine-jquery) 這樣測:

 #fixture.html
 <a href='#' class='trigger'>trigger</a>
 <div class='detail' style='display:none'>detail content</div>

 #spec
 describe "detail-trigger", ->

      beforeEach ->
           jasmine.getFixtures().fixturesPath = 'spec/fixtures'
           loadFixtures 'fixtures.html'

      it "should trigger target", ->
           trigger = $('.trigger')
           target = $('.detail')
           DetailTrigger.init(trigger, target)
           expect(target).toBeHidden()
           trigger.click()
           expect(target).toBeVisible()

這裏的測法,是直接在 DOM 上看看掛了上去的 event 有沒有被執行。這其實了測兩件事:event 有正確被綁定,jquery 本身的 on, clicktoggle 有在正常運作。對於一些潔癖者來說,這其實是測多了,比較像是 integration test,也依賴於 browser 環境執行,但我們真正寫的,其實只是綁定 event 而已。若將外傳的兩個 jQuery object 也好好地替代,就連 DOM fixture 也不用了:

 #spec
 describe "detail-trigger-in-mock", ->
      ctx = this

      beforeEach ()->
           ctx.jq = ()->
                on:(action, callback)->
                     @callback = callback
                toggle:->
                click:->
                     @callback()

      it "should bind trigger", ()->
           trigger = ctx.jq('.trigger')
           target = ctx.jq('.target')
           spyOn(trigger, 'on').andCallThrough()
           spyOn(target, 'toggle')

           DetailTrigger.init(trigger, target)
           expect(trigger.on.calls[0].args[0]).toEqual('click')
           trigger.click()
           expect(target.toggle).toHaveBeenCalled()

問題是,這樣寫要 mock 的東西會有很多 (jquery 本身就很多 method),尤其當你的 code 有大量 jQuery 相關的 dom manipulation 碼的時候會很慘。不過另一個角度看,也其實看出你的寫的 code 對 jQuery 有多依賴。

至於決定用那種測法?老答案:看情況。不過如果是簡單的,我會傾向 fixtures 法搞定。情況複雜的話,則要好好留心 code 是否寫得太 jQuery 向,混雜許多功能。

不過其實很多時,我都不太肯定...

P.S. 以上 code 見於 github

回應

*