about 1 year ago

double 可以讓我們輕易地閃掉準備「協作者」的痛苦。但是有時候,我們還是可能被「協作者」本身的內部邏輯改變整到。比如說,這裡是一個「建議」Suggestion 表單:

require 'rails_helper'

RSpec.describe SuggestionsController, type: :controller do

  describe "POST create" do 
    context "when suggestion is invalid" do 
      it "render new" do 
        post :create, :suggestion => { :title => "", :description => "" }

        expect(response).to render_template :new
      end
    end
  end
end

class Suggestion < ActiveRecord::Base
   validates :title, presence: true
end

class SuggestionsController 
  def create
    @suggestion = Suggestion.new(suggestion_params)
    if @suggestion.save
      redirect_to root_path
    else
      render :new
    end
  end

  protected

  def suggestion_params
    params.require(:suggestion).permit(:title, :description)
  end

end

如果物件非法的話,要 render new 這個 action。正常來說,這樣的 controller 測試,應該會通過。

但是有時候 controller 程式碼沒有動,但 model 程式碼內部動了,比如說在這個例子,可能多加了新的欄位,或者是新增了其他的驗證方式,導致這個 controller 測試要因為 suggestion 內部本身的邏輯要修改。

這樣真的很討厭。

重寫測試

其實這裡我們根本不應該塞「例子」進去,這樣 suggestion 內部邏輯一旦改變,我們的 controller test 就要改個沒完。我們真正應該要做的是:

  • 準備一個「一定儲存失敗的替身」
  • 然後在 controller 讓 @suggestion.save 鐵定失敗
  • 因為我們要驗證的是 「render :new」

所以我們可以把測試改寫成這樣

require 'rails_helper'

RSpec.describe SuggestionsController, type: :controller do

  describe "POST create" do 
    context "when suggestion is invalid" do 
      it "render new" do 
        invalid_suggestion = double(:save => false) 
        allow(Suggestion).to receive(:new).and_return(invalid_suggestion)

        post :create, :suggestion => { :xxx => "yyy" }

        expect(response).to render_template :new
      end
    end
  end
end

這樣 suggestion 內部驗證再怎麼樣變,都不會影響到這個 controller test。

← [RSpec] 進階測試系列概念 - Part 4 使用 double ( mock objects ) [RSpec] 進階測試系列概念 - Part 6 Mocking V.S. Spying →
 
comments powered by Disqus