【问题标题】:How can I test that my before_save callback does the right thing如何测试我的 before_save 回调是否正确
【发布时间】:2011-08-04 01:28:34
【问题描述】:

我的 ActiveRecord 模型上有一个回调,如下所示:

  before_save :sync_to_external_apis

  def sync_to_external_apis                                                                                                 
    [user, assoc_user].each {|cuser|
      if cuser.google_refresh
        display_user = other_user(cuser.id)
        api = Google.new(:user => cuser)
        contact = api.sync_user(display_user)
      end
    }
  end

我想编写一个 rspec 测试来测试调用保存!当 google_refresh 为 true 时,在此模型的实例上会导致在新的 Google 实例上调用 sync_user。我怎么能这样做?

【问题讨论】:

    标签: ruby-on-rails ruby activerecord rspec


    【解决方案1】:

    通常的测试方法是确保结果符合预期。由于您在这种情况下使用的 API 可能会使事情复杂化。您可能会发现,使用 mocha 创建一个可以发送 API 调用的模拟对象可以让您将 Google 类替换为同样适用于测试目的的东西。

    一个更简单但更笨重的方法是有一个“测试模式”开关:

    def sync_to_external_apis
      [ user, assoc_user ].each do |cuser|
        if (Rails.env.test?)
          @synced_users ||= [ ]
          @synced_users << cuser
        else
          # ...
        end
      end
    end
    
    def did_sync_user?(cuser)
      @synced_users and @synced_users.include?(cuser)
    end
    

    这是一种简单的方法,但它不会验证您的 API 调用是否正确。

    【讨论】:

    • 在生产代码中切换以进行测试是一个糟糕的主意。 -1
    • 我确实说过这是一种笨拙、次优的方法。但是,从实用的角度来看,有些东西需要在测试环境中禁用,因此您可能需要求助于这样的事情,除非您可以重新设计实现以避免这种依赖关系。拥有一个应用程序级开关sync_to_api? 无疑是朝着正确方向迈出的一步。不过,这类事情从原始问题中删除了几个级别。
    • 并且您应该在测试环境中中禁用它们。您的生产环境不应该对您的测试环境一无所知。曾经。 TDD 将有助于防止此类设计异味。
    • 我不太清楚从测试中禁用它的最佳方法是什么。感觉我需要从测试中使用不同的,可能是模拟/存根的 Google 类,但不确定这是否可能。
    • 解决很多这种混乱的一种方法是为 Google 类制作一个模块包装器,它通过一系列帮助方法为您进行同步。您可以定义像Refresher::Google.updateuser 这样的方法来处理调用,然后对其进行配置以避免测试环境中的冲突。这就像 ActionMailer 有不同的测试模式。
    【解决方案2】:
    it "should sync to external apis on save!" do
      model = Model.new
      model.expects(:sync_to_external_apis)
      model.save!
    end
    

    顺便说一句,在请求-响应周期中请求不可靠的资源(例如互联网)是一个坏主意。我建议改为创建一个后台作业。

    【讨论】:

    • 谢谢。这会测试是否调用了 sync_to_external_apis。但是如何测试该回调的实际功能呢?我需要检查是否在正确的条件下调用了 api.sync_user。
    【解决方案3】:

    摩卡是要走的路。我不熟悉 rspec,但这是您在测试单元中的做法:

    def test_google_api_gets_call_for_user_and_accoc_user user = mock('User') # 定义一个模拟对象并将其标记为 'User' accoc_user = mock('AssocUser') # 定义一个模拟对象并将其标记为'AssocUser' # 使用模拟对象实例化您正在测试的模型 model = Model.new(user, assoc_user) # 存根 other_user 方法。当模拟用户是时,它将返回 cuser1 # 传入mock assoc_user时传入和cuser2 cuser1 = 模拟('Cuser1') cuser2 = 模拟('Cuser2') model.expects(:other_user).with(user).returns(cuser1) model.expects(:other_user).with(assoc_user).returns(cuser2) # 设置对 Google API 的期望 api1 - mock('GoogleApiUser1') # 定义一个模拟对象并将其标记为 'GoogleApiUser1' api2 - mock('GoogleApiUser2') # 定义一个模拟对象并将其标记为 'GoogleApiUser2' # 在 Google 上调用 new 传入模拟用户并返回模拟 Google api 对象 Google.expects(:new).with(:user => cuser1).returns(api1) api1.expects(:sync_user).with(cuser1) Google.expects(:new).with(:user => cuser2).returns(api2) api2.expects(:sync_user).with(cuser2) # 现在执行应该满足上述所有期望的代码 模型。保存! 结尾

    上面的内容可能看起来很复杂,但不是一旦你掌握了它。您正在测试,当您调用 save 时,您的模型会执行它应该做的事情,但您没有真正与 API 对话、实例化数据库记录等的麻烦或时间开销。

    【讨论】:

      猜你喜欢
      • 2020-05-08
      • 1970-01-01
      • 2014-11-04
      • 1970-01-01
      • 1970-01-01
      • 2015-11-14
      • 2019-11-21
      • 1970-01-01
      • 2012-03-06
      相关资源
      最近更新 更多