RSpec の have_attributes マッチャがリテラルとマッチャを両方指定できる仕組み
have_attributes マッチャ
RSpec に have_attributes
というマッチャがある。
it { expect(10.to_s).to eq("10") } it { expect(10.positive?).to eq(true) }
と書くところを
it { expect(10).to have_attributes(to_s: "10", positive?: true) }
と書くことができる。
一つのインスタンスに対して複数の属性をチェックする時に読みやすくできるというメリットがある。
have_attributes の不思議
実は have_attributes
は
it { expect(10.positive).to be_truthy }
を
it { expect(10).to have_attributes(positive?: be_truthy) }
と書くことができる。 ハッシュのバリューに「マッチャ」を書くことができるのだ。 つまり have_attributes はハッシュのバリューに「マッチャ」と「リテラル」の両方を書くことができる。 これは結構不思議だと思ったので、RSpec のソースコードを読んでみた。
ソースコードを読む
この中でハッシュのキーとバリューはそれぞれ value_match?
で比較される。
def actual_has_attribute?(attribute_key, attribute_value) values_match?(attribute_value, @values.fetch(attribute_key)) end
value_match?
メソッドは Support::FuzzyMatcher.values_match?
を呼び出す。
def values_match?(expected, actual) expected = with_matchers_cloned(expected) Support::FuzzyMatcher.values_match?(expected, actual) end
この FuzzyMatcher.values_match?
メソッドが、expected
と actual
を ==
で比較し、不一致だったら更に matcher.match?
で比較するという仕組みになっている。
def self.values_match?(expected, actual) if Hash === actual return hashes_match?(expected, actual) if Hash === expected elsif Array === expected && Enumerable === actual && !(Struct === actual) return arrays_match?(expected, actual.to_a) end return true if expected == actual begin expected === actual rescue ArgumentError # Some objects, like 0-arg lambdas on 1.9+, raise # ArgumentError for `expected === actual`. false end end
だから have_attributes
にはマッチャもリテラルも両方書ける!
まとめ
たまにはソースコードを読んでみると面白い。