當程式碼依賴外部系統(信用卡閘道、郵件伺服器、第三方 API)時,測試若直接呼叫真實服務會:
Mock 物件以可控的假實作替換真實依賴,解決上述問題。
在 test/mocks/#{RAILS_ENV}/ 目錄建立同名檔案,即可覆蓋 app/models/ 對應類別:
# app/models/payment_gateway.rb (真實實作)
class PaymentGateway
def collect(options)
# 真實呼叫銀行 API
Net::HTTP.post_form(URI.parse('https://bank.api/collect'), options)
end
end
# test/mocks/test/payment_gateway.rb (測試用 Mock)
require 'models/payment_gateway'
class PaymentGateway
def collect(request)
# 直接回傳成功,不連網路
:success
end
end
測試環境載入類別時,優先搜尋 test/mocks/test/,自動使用 Mock 版本。
# store_controller.rb
def save_order
@cart = find_cart
@order = Order.new(params[:order])
@order.line_items << @cart.items
gateway = PaymentGateway.new
response = gateway.collect(
:login => 'username',
:password => 'password',
:amount => @cart.total_price,
:card_number => @order.card_number,
:expiration => @order.card_expiration,
:name => @order.name
)
if response == :success
@order.save
@cart.empty!
redirect_to_index("Thank you for your order")
else
flash[:notice] = "Payment failed"
render :action => 'checkout'
end
end
測試時自動使用 Mock,collect 永遠回傳 :success,測試可專注於訂單儲存、購物車清空、導向邏輯。
gem install mocha
# test_helper.rb
require 'mocha/setup'
# 測試中
def test_save_order_with_mock
gateway_mock = mock('PaymentGateway')
gateway_mock.expects(:collect).with(
has_entries(:amount => 29.95, :card_number => '4111111111111111')
).returns(:success)
PaymentGateway.stubs(:new).returns(gateway_mock)
post :save_order, :order => valid_order_attributes
assert_redirected_to :action => 'index'
end
優點:
gateway_mock.expects(:collect).raises(Timeout::Error)
# 或
gateway_mock.expects(:collect).returns(:declined)
輕鬆測試網路逾時、卡片被拒等異常分支。
| 特性 | Mock | Stub |
|---|---|---|
| 目的 | 驗證互動行為(有無呼叫、參數正確) | 提供預設回應,隔離依賴 |
| 失敗模式 | 期望不符 → 測試失敗 | 不驗證呼叫,僅提供資料 |
| 典型用途 | 命令型操作(寄信、扣款、發通知) | 查詢型操作(取設定、讀快取、查 API) |
test_should_charge_credit_card_on_save_orderstub_chain 簡化深層呼叫鏈:User.stubs(:find).with(1).returns(user_stub)