自訂 RSpec Matcher
RSpec 內建的 matchers,比如:have_attributes
, eq
, match
等等,不僅提供了富表達性的語法,也有更清晰的錯誤訊息。
當我們有一個常用的檢查時,就可以考慮自己定義一個 Matcher,來增加測試的表達性,以及錯誤的可閱讀性。自己寫 Matcher 的壞處就是增加學習成本,除了 RSpec 本身提供的 Matchers 以外,團隊成員都要花心力去學習這個,自己定義的新 Matcher。
接下來看怎麼自己寫一個簡單的 Matcher,比如可以檢查 object instance variables 的 matcher。
沒有用 Matcher 之前:
expect(obj.instance_variable_get("@foo")).to eq "bar"
expect(obj.instance_variable_get("@bar")).to eq "baz"
使用我們自己定義的 matcher:
expect(obj).to have_instance_variables(
"@foo" => "bar",
"@bar" => "baz"
)
具體實現:
RSpec::Matchers.define(:have_instance_variables) do |expected|
match do |actual|
expected.each do |instance_variable, expected_value|
expect(actual.instance_variable_get(instance_variable)).to eq(expected_value)
end
end
failure_message do |actual|
expected.each do |instance_variable, expected_value|
actual_value = actual.instance_variable_get(instance_variable)
if actual_value != expected_value
return "expected #{actual}#{instance_variable} to match #{expected_value.inspect}, but got #{actual_value.inspect}."
end
end
end
failure_message_when_negated do |actual|
expected.each do |instance_variable, expected_value|
actual_value = actual.instance_variable_get(instance_variable)
if actual_value == expected_value
return "expected #{actual}#{instance_variable} not to match #{expected_value.inspect}, but got #{actual_value.inspect}."
end
end
end
end
RSpec::Matchers.define(:have_instance_variables)
RSpec 的 to
後面接的便是 matcher 的名字:
expect(obj).to matcher_name(...)
RSpec::Matchers.define
接受的 symbol 便是自定義 Matcher 的名字:
expect(obj).to have_instance_variables(
"@foo" => "bar",
"@bar" => "baz"
)
have_instance_variables(
"@foo" => "bar",
"@bar" => "baz"
)
其實就是傳入了一個 Hash 給 have_instance_variables
:{ "@foo" => "bar", "@bar" => "baz" }
。
而這個 Hash 可以在 RSpec::Matchers.define(:have_instance_variables) do |expected|
區塊的 expected
參數拿到。
匹配的邏輯寫在 match
區塊裡,傳進來的 actual
便是 expect(obj)
的 obj
:
match do |actual|
# 匹配的邏輯寫在這裡
end
在這裡我們希望對象的 instance variable 與我們的期望值相等:
expected.each do |instance_variable, expected_value|
expect(actual.instance_variable_get(instance_variable)).to eq(expected_value)
end
to
& not_to
要顯示的錯誤訊息分別定義在 failure_message
以及 failure_message_when_negated
區塊裡。
更多請參考 RSpec 客製化 matcher 的文件:https://www.relishapp.com/rspec/rspec-expectations/v/3-3/docs/custom-matchers/define-matcher。