【问题标题】:Rails RSpec, DRY specs: shared example vs. helper method vs. custom matcherRails RSpec,DRY 规范:共享示例 vs. 辅助方法 vs. 自定义匹配器
【发布时间】:2017-06-16 20:42:00
【问题描述】:

我对控制器规范中的每个 HTTP 方法/控制器操作组合重复了一次以下测试:

it "requires authentication" do
  get :show, id: project.id
  # Unauthenticated users should be redirected to the login page
  expect(response).to redirect_to new_user_session_path
end

我找到了以下三种方法来重构它并消除重复。哪个最合适?

共享示例

在我看来,共享示例是最合适的解决方案。但是,为了将params 传递给共享示例,必须使用块感觉有点尴尬。

shared_examples "requires authentication" do |http_method, action|
  it "requires authentication" do
    process(action, http_method.to_s, params)
    expect(response).to redirect_to new_user_session_path
  end
end

RSpec.describe ProjectsController, type: :controller do
  describe "GET show", :focus do
    let(:project) { Project.create(name: "Project Rigpa") }

    include_examples "requires authentication", :GET, :show do
      let(:params) { {id: project.id} }
    end
  end
end

辅助方法

这具有不需要块将project.id 传递给辅助方法的优点。

RSpec.describe ProjectsController, type: :controller do
  def require_authentication(http_method, action, params)
    process(action, http_method.to_s, params)
    expect(response).to redirect_to new_user_session_path
  end

  describe "GET show", :focus do
    let(:project) { Project.create(name: "Project Rigpa") }

    it "requires authentication" do
      require_authentication(:GET, :show, id: project.id )
    end
  end
end

自定义匹配器

如果有单行测试就好了。

RSpec::Matchers.define :require_authentication do |http_method, action, params|
  match do
    process(action, http_method.to_s, params)
    expect(response).to redirect_to Rails.application.routes.url_helpers.new_user_session_path
  end
end

RSpec.describe ProjectsController, type: :controller do
  describe "GET show", :focus do
    let(:project) { Project.create(name: "Project Rigpa") }

    it { is_expected.to require_authentication(:GET, :show, {id: project.id}) }
  end
end

提前致谢。

【问题讨论】:

  • 我相信您可以将辅助方法移动到 /support/helpers 并通过包含在您的其他控制器中利用它们。自定义匹配器可能是 rspec 方式,但谁在乎它是否适合你
  • 我喜欢循序渐进的方法,所以可能先使用辅助方法,当我在其他地方看到它有用时,再将其重构为自定义匹配器

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


【解决方案1】:

didroe 在this Reddit post 中提供的一个建议让我认为将方法/操作调用 (process) 放在共享代码中并不是一个好主意,因为它会增加复杂性(降低可读性)并且实际上不会减少代码重复.

在搜索了更多内容后,我在Everyday Rails Testing with RSpec by Aaron Sumner 书中找到了我认为最好的选择(第 102 页)。

创建以下自定义匹配器:

# spec/support/matchers/require_login.rb
RSpec::Matchers.define :require_login do |expected|
  match do |actual|
    expect(actual).to redirect_to \
      Rails.application.routes.url_helpers.new_user_session_path
  end

  failure_message do |actual|
    "expected to require login to access the method"
  end

  failure_message_when_negated do |actual|
    "expected not to require login to access the method"
  end

  description do
    "redirect to the login form"
  end
end

并对每个控制器的每个动作使用如下测试:

it "requires authentication" do
  get :show, id: project.id
  expect(response).to require_login
end

与在所有测试中重复expect(response).to redirect_to new_user_session_path 相比,这种方法具有以下优点:

  • 提高了可维护性。如果我们最终必须更改此断言,我们只需在一个地方更改它,而不必更改数十或数百个测试。
  • 更好的失败消息。

你怎么看?

【讨论】:

    【解决方案2】:

    在您描述的情况下,我会选择 RSpec 自定义匹配器。它们使您的规范更易于阅读并且更接近您的应用程序领域。 https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher

    我会使用 shared_examples 来指定更复杂的场景,并调用 it_behaves_like 在不同的上下文中一次检查所有内容。

    如果可能,您应该尽量避免使用辅助方法,并且仅在有助于保持规范整洁的情况下在单个文件中使用它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-10
      相关资源
      最近更新 更多