我可以在自定义匹配器中使用内置 RSpec 匹配器吗?
Can I use a built-in RSpec matcher in a custom matcher?
我对功能规范有以下期望(相当低级但仍然必要):
expect(Addressable::URI.parse(current_url).query_values).to include(
'some => 'value',
'some_other' => String
)
请注意,第二个查询值是模糊匹配,因为我只想确保它存在,但我无法对其进行更具体的说明。
我想将其提取到自定义匹配器中。我开始于:
RSpec::Matchers.define :have_query_params do |expected_params|
match do |url|
Addressable::URI.parse(url).query_values == expected_params
end
end
但这意味着我不能在那里传递 {'some_other' => String}
。要继续使用模糊匹配,我必须在我的自定义匹配器中使用 include
匹配器。
但是,RSpec::Matchers::BuiltIn
中的任何内容都被标记为私有 API,而 Include
特别是 documented 为:
# Provides the implementation for `include`.
# Not intended to be instantiated directly.
所以,我的问题是:是否在 RSpec 支持的自定义匹配器中使用内置匹配器? 我该怎么做?
RSpec::Matchers
appears to be a supported API (its rdoc doesn't say otherwise), so you can write your matcher in Ruby instead of in the matcher DSL (which is supported; see the second paragraph of the custom matcher documentation) 并像这样使用 RSpec::Matchers#include
:
spec/support/matchers.rb
module My
module Matchers
def have_query_params(expected)
HasQueryParams.new expected
end
class HasQueryParams
include RSpec::Matchers
def initialize(expected)
@expected = expected
end
def matches?(url)
actual = Addressable::URI.parse(url).query_values
@matcher = include @expected
@matcher.matches? actual
end
def failure_message
@matcher.failure_message
end
end
end
end
spec/support/matcher_spec.rb
include My::Matchers
describe My::Matchers::HasQueryParams do
it "matches" do
expect("http://example.com?a=1&b=2").to have_query_params('a' => '1', 'b' => '2')
end
end
是的,您可以从自定义匹配器中调用内置 rspec 匹配器。换句话说,您可以在编写匹配器时使用普通的 Rspec DSL 而不是纯 Ruby。查看 this gist(不是我的要点,但对我有帮助!)。
我有一个非常复杂的控制器,它有一个选项卡式界面,其中定义和选择的选项卡取决于模型实例的状态。我需要在 :new、:create、:edit 和 :update 操作的每个状态下测试选项卡设置。所以我写了这些匹配器:
require "rspec/expectations"
RSpec::Matchers.define :define_the_review_tabs do
match do
expect(assigns(:roles )).to be_a_kind_of(Array)
expect(assigns(:creators )).to be_a_kind_of(ActiveRecord::Relation)
expect(assigns(:works )).to be_a_kind_of(Array)
expect(assigns(:available_tabs)).to include("post-new-work")
expect(assigns(:available_tabs)).to include("post-choose-work")
end
match_when_negated do
expect(assigns(:roles )).to_not be_a_kind_of(Array)
expect(assigns(:creators )).to_not be_a_kind_of(ActiveRecord::Relation)
expect(assigns(:works )).to_not be_a_kind_of(Array)
expect(assigns(:available_tabs)).to_not include("post-new-work")
expect(assigns(:available_tabs)).to_not include("post-choose-work")
end
failure_message do
"expected to set up the review tabs, but did not"
end
failure_message_when_negated do
"expected not to set up review tabs, but they did"
end
end
RSpec::Matchers.define :define_the_standalone_tab do
match do
expect(assigns(:available_tabs)).to include("post-standalone")
end
match_when_negated do
expect(assigns(:available_tabs)).to_not include("post-standalone")
end
failure_message do
"expected to set up the standalone tab, but did not"
end
failure_message_when_negated do
"expected not to set up standalone tab, but they did"
end
end
RSpec::Matchers.define :define_only_the_review_tabs do
match do
expect(assigns).to define_the_review_tabs
expect(assigns).to_not define_the_standalone_tab
expect(assigns(:selected_tab)).to eq(@selected) if @selected
end
chain :and_select do |selected|
@selected = selected
end
failure_message do
if @selected
"expected to set up only the review tabs and select #{@selected}, but did not"
else
"expected to set up only the review tabs, but did not"
end
end
end
RSpec::Matchers.define :define_only_the_standalone_tab do
match do
expect(assigns).to define_the_standalone_tab
expect(assigns).to_not define_the_review_tabs
expect(assigns(:selected_tab)).to eq("post-standalone")
end
failure_message do
"expected to set up only the standalone tab, but did not"
end
end
RSpec::Matchers.define :define_all_tabs do
match do
expect(assigns).to define_the_review_tabs
expect(assigns).to define_the_standalone_tab
expect(assigns(:selected_tab)).to eq(@selected) if @selected
end
chain :and_select do |selected|
@selected = selected
end
failure_message do
if @selected
"expected to set up all three tabs and select #{@selected}, but did not"
else
"expected to set up all three tabs, but did not"
end
end
end
我是这样使用它们的:
should define_all_tabs.and_select("post-choose-work")
should define_all_tabs.and_select("post-standalone")
should define_only_the_standalone_tab
should define_only_the_review_tabs.and_select("post-choose-work")
should define_only_the_review_tabs.and_select("post-new-work")
能够将几块重复的期望并将它们汇总到一组自定义匹配器中,而无需在纯 Ruby.
中编写匹配器,真是太棒了
这为我节省了几十行代码,使我的测试更具表现力,并且允许我在填充这些选项卡的逻辑发生变化时在一处进行更改。
另请注意,您可以在自定义匹配器中访问 methods/variables,例如 assigns
和 controller
,因此您无需显式传递它们。
最后,我可以在规范中内联定义这些匹配器,但我选择将它们放在 spec/support/matchers/controllers/posts_controller_matchers.rb
中
您可以使用匹配器 DSL 而不是自己编写 Matcher
class。比较简单
RSpec::Matchers.define :have_query_params do |expected|
match do |actual|
# your code
RSpec::Matchers::BuiltIn::Include.new(expected).matches?(actual)
end
end
我对功能规范有以下期望(相当低级但仍然必要):
expect(Addressable::URI.parse(current_url).query_values).to include(
'some => 'value',
'some_other' => String
)
请注意,第二个查询值是模糊匹配,因为我只想确保它存在,但我无法对其进行更具体的说明。
我想将其提取到自定义匹配器中。我开始于:
RSpec::Matchers.define :have_query_params do |expected_params|
match do |url|
Addressable::URI.parse(url).query_values == expected_params
end
end
但这意味着我不能在那里传递 {'some_other' => String}
。要继续使用模糊匹配,我必须在我的自定义匹配器中使用 include
匹配器。
但是,RSpec::Matchers::BuiltIn
中的任何内容都被标记为私有 API,而 Include
特别是 documented 为:
# Provides the implementation for `include`.
# Not intended to be instantiated directly.
所以,我的问题是:是否在 RSpec 支持的自定义匹配器中使用内置匹配器? 我该怎么做?
RSpec::Matchers
appears to be a supported API (its rdoc doesn't say otherwise), so you can write your matcher in Ruby instead of in the matcher DSL (which is supported; see the second paragraph of the custom matcher documentation) 并像这样使用 RSpec::Matchers#include
:
spec/support/matchers.rb
module My
module Matchers
def have_query_params(expected)
HasQueryParams.new expected
end
class HasQueryParams
include RSpec::Matchers
def initialize(expected)
@expected = expected
end
def matches?(url)
actual = Addressable::URI.parse(url).query_values
@matcher = include @expected
@matcher.matches? actual
end
def failure_message
@matcher.failure_message
end
end
end
end
spec/support/matcher_spec.rb
include My::Matchers
describe My::Matchers::HasQueryParams do
it "matches" do
expect("http://example.com?a=1&b=2").to have_query_params('a' => '1', 'b' => '2')
end
end
是的,您可以从自定义匹配器中调用内置 rspec 匹配器。换句话说,您可以在编写匹配器时使用普通的 Rspec DSL 而不是纯 Ruby。查看 this gist(不是我的要点,但对我有帮助!)。
我有一个非常复杂的控制器,它有一个选项卡式界面,其中定义和选择的选项卡取决于模型实例的状态。我需要在 :new、:create、:edit 和 :update 操作的每个状态下测试选项卡设置。所以我写了这些匹配器:
require "rspec/expectations"
RSpec::Matchers.define :define_the_review_tabs do
match do
expect(assigns(:roles )).to be_a_kind_of(Array)
expect(assigns(:creators )).to be_a_kind_of(ActiveRecord::Relation)
expect(assigns(:works )).to be_a_kind_of(Array)
expect(assigns(:available_tabs)).to include("post-new-work")
expect(assigns(:available_tabs)).to include("post-choose-work")
end
match_when_negated do
expect(assigns(:roles )).to_not be_a_kind_of(Array)
expect(assigns(:creators )).to_not be_a_kind_of(ActiveRecord::Relation)
expect(assigns(:works )).to_not be_a_kind_of(Array)
expect(assigns(:available_tabs)).to_not include("post-new-work")
expect(assigns(:available_tabs)).to_not include("post-choose-work")
end
failure_message do
"expected to set up the review tabs, but did not"
end
failure_message_when_negated do
"expected not to set up review tabs, but they did"
end
end
RSpec::Matchers.define :define_the_standalone_tab do
match do
expect(assigns(:available_tabs)).to include("post-standalone")
end
match_when_negated do
expect(assigns(:available_tabs)).to_not include("post-standalone")
end
failure_message do
"expected to set up the standalone tab, but did not"
end
failure_message_when_negated do
"expected not to set up standalone tab, but they did"
end
end
RSpec::Matchers.define :define_only_the_review_tabs do
match do
expect(assigns).to define_the_review_tabs
expect(assigns).to_not define_the_standalone_tab
expect(assigns(:selected_tab)).to eq(@selected) if @selected
end
chain :and_select do |selected|
@selected = selected
end
failure_message do
if @selected
"expected to set up only the review tabs and select #{@selected}, but did not"
else
"expected to set up only the review tabs, but did not"
end
end
end
RSpec::Matchers.define :define_only_the_standalone_tab do
match do
expect(assigns).to define_the_standalone_tab
expect(assigns).to_not define_the_review_tabs
expect(assigns(:selected_tab)).to eq("post-standalone")
end
failure_message do
"expected to set up only the standalone tab, but did not"
end
end
RSpec::Matchers.define :define_all_tabs do
match do
expect(assigns).to define_the_review_tabs
expect(assigns).to define_the_standalone_tab
expect(assigns(:selected_tab)).to eq(@selected) if @selected
end
chain :and_select do |selected|
@selected = selected
end
failure_message do
if @selected
"expected to set up all three tabs and select #{@selected}, but did not"
else
"expected to set up all three tabs, but did not"
end
end
end
我是这样使用它们的:
should define_all_tabs.and_select("post-choose-work")
should define_all_tabs.and_select("post-standalone")
should define_only_the_standalone_tab
should define_only_the_review_tabs.and_select("post-choose-work")
should define_only_the_review_tabs.and_select("post-new-work")
能够将几块重复的期望并将它们汇总到一组自定义匹配器中,而无需在纯 Ruby.
中编写匹配器,真是太棒了这为我节省了几十行代码,使我的测试更具表现力,并且允许我在填充这些选项卡的逻辑发生变化时在一处进行更改。
另请注意,您可以在自定义匹配器中访问 methods/variables,例如 assigns
和 controller
,因此您无需显式传递它们。
最后,我可以在规范中内联定义这些匹配器,但我选择将它们放在 spec/support/matchers/controllers/posts_controller_matchers.rb
中您可以使用匹配器 DSL 而不是自己编写 Matcher
class。比较简单
RSpec::Matchers.define :have_query_params do |expected|
match do |actual|
# your code
RSpec::Matchers::BuiltIn::Include.new(expected).matches?(actual)
end
end