【问题标题】:Rails 5 Rspec receive with ActionController::ParamsRails 5 Rspec 通过 ActionController::Params 接收
【发布时间】:2016-09-26 12:33:02
【问题描述】:

我刚刚升级到 Rails 5。在我的规格中,我有以下内容

expect(model).to receive(:update).with(foo: 'bar')

但是,由于 params 不再扩展 Hash 而现在是 ActionController::Parameters,因此规范失败了,因为 with() 期待一个哈希值,但它实际上是 ActionController::Parameters

有没有更好的方法在 Rspec 中做同样的事情,比如不同的方法with_hash

我可以使用

解决这个问题

expect(model).to receive(:update).with(hash_including(foo: 'bar'))

但这只是检查参数是否包含该哈希,而不是检查完全匹配。

【问题讨论】:

    标签: ruby-on-rails ruby rspec ruby-on-rails-5


    【解决方案1】:

    你可以这样做:

    params = ActionController::Parameters.new(foo: 'bar')
    expect(model).to receive(:update).with(params)
    

    但是它仍然有味道 - 您应该测试应用程序的行为 - 而不是它的工作方式。

    expect {
      patch model_path(model), params: { foo: 'bar' }
      model.reload
    }.to change(model, :foo).to('bar')
    

    这就是我测试控制器集成的方式:

    require 'rails_helper'
    RSpec.describe "Things", type: :request do
      describe "PATCH /things/:id" do
    
        let!(:thing) { create(:thing) }
        let(:action) do
          patch things_path(thing), params: { thing: attributes }
        end
    
        context "with invalid params" do
          let(:attributes) { { name: '' } }
          it "does not alter the thing" do
             expect do 
               action 
               thing.reload
             end.to_not change(thing, :name)
             expect(response).to have_status :bad_entity
          end
        end
    
        context "with valid params" do
          let(:attributes) { { name: 'Foo' } }
           it "updates the thing" do
             expect do 
               action 
               thing.reload
             end.to change(thing, :name).to('Foo')
             expect(response).to be_successful
          end
        end
      end
    end
    

    在规范中接触数据库是否会很糟糕?

    没有。当您测试控制器之类的东西时,最准确的测试方法是驱动整个堆栈。如果我们在这种情况下删除了@thing.update,我们可能会错过例如数据库驱动程序抛出错误,因为我们使用了错误的 SQL 语法。

    例如,如果您要在模型上测试范围,那么剔除 DB 的规范几乎不会给您带来任何价值。

    存根可能会为您提供一个快速测试套件,该套件由于紧密耦合而非常脆弱,并且会让大量错误从裂缝中溜走。

    【讨论】:

    • 我一直认为在规范内访问数据库是一件坏事?这就是为什么我总是在模型上存根该方法,然后如果我需要检查valid? 等,则返回一个双精度值。这样我可以期望双精度接收valid? 并根据我想要测试的内容(成功或失败)返回真或假
    • 这有点夸张——在单元测试中存根数据库是一个好主意,可以让你的测试套件更快。但是,当您在测试控制器之类的东西时,您真正在做的是功能测试或集成测试,而所有这些存根都会破坏测试的敏锐度。
    • @max 我不会推荐任何想成为优秀开发人员的人关注 dhh 的任何文章或他的代码
    • 那么@RustamA.Gasanov 没有适合你的轨道。
    • 我同意不删除模型。但是,如果我需要测试控制器调用的(服务)对象,并且根据输入执行各种操作,那么单独测试该对象会容易得多,并且在控制器中只测试它是否通过了它的所有内容需要那个对象。功能规格通常应该测试所有内容也很好地集成在基本场景中。
    【解决方案2】:

    我通过在 spec/rails_helper.rb 中创建来处理这个

    def strong_params(wimpy_params)
      ActionController::Parameters.new(wimpy_params).permit!
    end
    

    然后在特定的测试中,你可以说:

    expect(model).to receive(:update).with(strong_params foo: 'bar')
    

    这与您已经在做的并没有太大区别,但它使额外调用的尴尬必要性在语义上更有意义。

    【讨论】:

      【解决方案3】:

      @max 对如何完全避免这种情况提出了很好的建议,我同意他们从散列切换到不鼓励将它们与散列互换使用。

      但是,如果您仍然想使用它们,作为更复杂情况的简单 hack(例如,如果您 expect 使用 a_hash_including),您可以尝试使用类似这样的东西:

        .with( an_object_satisfying { |o| 
                 o.slice(some_params) == ActionController::Parameters.new(some_params)
               })
      

      【讨论】:

      • 感谢您的回答,我在规范中实施了一个 hack,同时我决定采用更好的方法。我基本上添加了一个to_params 方法,该方法调用ActionController::Parameters.new hash,以便将规范中的哈希转换为ActionController::Paramsexpect(foo).to receive(:bar).with(to_params(foo: 'bar'))
      • 你猴子修补了 to_params 方法到 ActionController::Parameters
      • 在评论中的例子中,foo: 'bar'被转换为ActionController::Parameters的实例
      • 嗯,好的。是的,可以轻松做到这一点。我使用了上述技术,因为我使用的是a_hash_including,它不允许进行那种简单的转换。这些都不是特别理想。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-02
      • 1970-01-01
      • 1970-01-01
      • 2017-11-13
      • 2016-11-14
      相关资源
      最近更新 更多