【问题标题】:Testing an association model helper method rails rspec测试关联模型辅助方法 rails rspec
【发布时间】:2016-02-11 00:58:00
【问题描述】:

我有两个模型,UserAccount

# account.rb
belongs_to :user

# user.rb
has_one :account

Account 有一个属性name。在我看来,我多次致电current_user.account.name,我听说这不是一个很好的方法。所以我的速度非常快,我在user.rb中创建了以下方法

def account_name
  self.account.name
end

所以现在在我看来,我可以简单地调用current_user.account_name,如果关联发生变化,我只会在一个地方更新它。但我的问题是,我要测试这种方法吗?如果我这样做了,我如何在没有神秘客人的情况下对其进行测试?

【问题讨论】:

    标签: ruby-on-rails ruby unit-testing rspec


    【解决方案1】:

    我同意 current_user.account.name 没有任何问题 - 虽然 Sandi Metz 会告诉我们“用户对 Account 了解太多”,但这是你无法通过 Active Record 真正避免的事情。

    如果您发现自己在 User 模型中使用了很多这些方法,则可以使用 rails 委托方法:

      delegate :name, :to => :account, :prefix => true
    

    使用 :prefix => true 选项将为 User 模型中的方法添加前缀,因此它是 account_name。在这种情况下,我假设您可以在它返回 something 的方法上编写一个非常简单的单元测试,以防万一帐户中的属性会改变您的测试将失败,因此您知道需要更新委托方法。

    【讨论】:

    • 感谢delegate 选项。这对我来说很新鲜。而且,公平地说,使用current_user.account.name 可能没什么问题,但是,你会写一个测试来测试account.name 不会改变吗?类似于您建议为建议的代码编写测试的方式?
    • 如果你使用delegate,你只需要测试目标对象(在本例中是Account)是否响应:name。 Rails 库测试将处理测试他们对 delegate 方法的风格 - 所以就您而言,使用委托将删除一些方法链接,您将仅测试在发送 account_name 的用户中创建的委托者一些东西并得到一些东西(不是NoMethodError
    • 谢谢,这很有道理
    【解决方案2】:
    1. current_user.account.name 没有问题
    2. 将其称为 current_user.account.name 或让 current_user.account_name 为您调用它没有区别
    3. 你可能没有像你说的那样在模型中调用 current_user
    4. 如果你使用它,你应该有一个规范

    就我个人而言,我认为这没有充分的理由。只需使用 current_user.account.name。

    如果您担心效率,让 current_user 返回一个加入帐户的用户。

    【讨论】:

      【解决方案3】:

      这有点跑题了。因此,如果它不有趣或没有帮助,请提前道歉。

      TL;DR:不要将模型知识放在视图中。保持你的控制器瘦。以下是我的做法。

      在我当前的项目中,我一直在努力确保我的视图完全不了解系统的其余部分(以减少耦合)。这样,如果您决定更改实现某些东西的方式(例如,current_user.account.namecurrent_user.account_name),那么您不必进入您的视图并进行更改。

      每个控制器操作都提供一个@results 哈希,其中包含视图正确呈现所需的所有内容。 @results 哈希的结构本质上是视图和控制器之间的契约。

      所以,在我的控制器中,@results 可能看起来像 {current_user: {account: {name: 'foo'}}}。在我看来,我会做类似@results[:current_user][:account][:name] 的事情。我喜欢使用HashWithIndifferentAccess,所以我也可以使用@results['current_user']['account']['name'],而不会出现问题或行为不端。

      此外,我一直在将尽可能多的逻辑从控制器转移到服务对象(我称它们为“经理”)。我发现我的经理(他们是 PORO)比控制器更容易测试。所以,我可能有:

      # app/controllers/some_controller.rb
      class SomeController
        def create
          @results = SomeManager.create(params)
          if @results[:success]
            # happy routing
          else
            # sad routing
          end
        end
      end
      

      现在,我的控制器非常瘦,除了路由之外不包含任何逻辑。他们对我的模型一无所知。 (事实上​​,几乎我所有的控制器操作看起来都完全一样,基本上都是相同的六行代码。)我喜欢这个,因为它创造了分离。

      当然,我需要经理:

      #app/managers/some_manager.rb
      class SomeManager
        class << self
          def create(params)
            # do stuff that ends up creating the @results hash
            # if things went well, the return will include success: true
            # if things did not go well, the return will not include a :success key
          end
        end
      end
      

      所以,事实上,@results 的结构是视图和管理器之间的契约,而不是视图和控制器之间的契约。

      【讨论】:

      • 感谢您的回答。我非常感激。但是你有没有遇到我上面描述的同样的问题,如果'name'属性更改为'title',你不需要从@results['current_user']['account']['name 改变你的视图调用吗? '] 到 @results['current_user']['account']['title']?
      • 您不一定会遇到同样的问题。即使 'name' 值实际上来自 current_user.account.title,您的哈希值仍可能是 @results['current_user']['account']['name']。您可以更改管理器中的哈希构造逻辑以使用 'title' 作为 'name' 值,而您的视图将不明智。
      • 酷,我明白这如何让生活更轻松。您将如何使用该更改更新哈希?抱歉,如果回答起来很复杂
      • 假设 (a) 控制器是 UserController,(b) 动作是显示,(c) 参数包括 :id。在 UserManager.show 中,您可能会执行以下操作: user = User.find(params[:id]); @results = HashWithIndifferentAccess.new; @results[:current_user] = HashWithIndifferentAccess.new(user.as_json); @results[:current_user][:account] = HashWithIndifferentAccess.new(user.account.as_json); @results[:current_user][:account][:name] = user.account.title
      猜你喜欢
      • 2013-05-11
      • 1970-01-01
      • 2014-02-10
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 2011-08-03
      • 2013-03-06
      • 2014-11-19
      相关资源
      最近更新 更多