首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Ruby on Rails 開發從頭來(二十六):使用 Mock 物件隔離外部依賴

     編輯:Java 相關知識

為何需要 Mock 物件?

當程式碼依賴外部系統(信用卡閘道、郵件伺服器、第三方 API)時,測試若直接呼叫真實服務會:

  • 執行緩慢(網路延遲)
  • 結果不穩定(服務暫停、限流)
  • 產生副作用(真實扣款、寄送郵件)
  • 難以模擬錯誤情境(逾時、拒絕、異常回應)

Mock 物件以可控的假實作替換真實依賴,解決上述問題。

Rails 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,測試可專注於訂單儲存、購物車清空、導向邏輯。

進階 Mock 技巧

使用 Mocha/FlexMock 庫(現代推薦)

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

優點:

  • 宣告式期望設定(參數匹配、呼叫次數、回傳值)
  • 無需建立實體 Mock 檔案
  • 支援 stubs/expects 區分「可選」與「必須」呼叫

模擬錯誤情境

gateway_mock.expects(:collect).raises(Timeout::Error)
# 或
gateway_mock.expects(:collect).returns(:declined)

輕鬆測試網路逾時、卡片被拒等異常分支。

Mock 與 Stub 差異

特性MockStub
目的驗證互動行為(有無呼叫、參數正確)提供預設回應,隔離依賴
失敗模式期望不符 → 測試失敗不驗證呼叫,僅提供資料
典型用途命令型操作(寄信、扣款、發通知)查詢型操作(取設定、讀快取、查 API)

最佳實務

  • 僅 Mock 外部邊界(資料庫、網路、檔案系統、時間),不要 Mock 內部領域物件
  • 測試名稱反映被驗證行為:test_should_charge_credit_card_on_save_order
  • 使用 stub_chain 簡化深層呼叫鏈:User.stubs(:find).with(1).returns(user_stub)
  • 定期檢視 Mock 是否與真實介面同步,避免虛假通過
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved