Rails RSpec,DRY 规范:共享示例与辅助方法与自定义匹配器
Rails RSpec, DRY specs: shared example vs. helper method vs. custom matcher
我对控制器规范中的每个 HTTP method/controller 操作组合重复了一次以下测试:
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
提前致谢。
在您描述的情况下,我会选择 RSpec 自定义匹配器。它们使您的规范更易于阅读并更接近您的应用程序领域。
https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher
我会使用 shared_examples 来指定更复杂的场景,并调用 it_behaves_like 在不同的上下文中一次检查所有内容。
您应该尽可能避免使用辅助方法,并且仅在有助于保持规范清洁的情况下才在单个文件中使用它们。
didroe 在 this Reddit post 中提供的建议让我想到将 method/action 调用 (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
相比,这种方法有以下优点:
- 改进了可维护性。如果我们最终必须更改此断言,我们会在一个地方更改它,而不必更改数十个或数百个测试。
- 更好的失败消息。
你怎么看?
我对控制器规范中的每个 HTTP method/controller 操作组合重复了一次以下测试:
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
提前致谢。
在您描述的情况下,我会选择 RSpec 自定义匹配器。它们使您的规范更易于阅读并更接近您的应用程序领域。 https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher
我会使用 shared_examples 来指定更复杂的场景,并调用 it_behaves_like 在不同的上下文中一次检查所有内容。
您应该尽可能避免使用辅助方法,并且仅在有助于保持规范清洁的情况下才在单个文件中使用它们。
didroe 在 this Reddit post 中提供的建议让我想到将 method/action 调用 (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
相比,这种方法有以下优点:
- 改进了可维护性。如果我们最终必须更改此断言,我们会在一个地方更改它,而不必更改数十个或数百个测试。
- 更好的失败消息。
你怎么看?