have_content? and has_content? is different in RSpec
RSpecのsystem specで have_content? と has_content? を間違えていて、ドハマりしました。 💥💥
結論
has_content? と have_content? は別物!条件分岐に使うときは注意!
環境
🌸 ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
🌸 bin/rails -v
Rails 5.2.0
🌸 be rspec -v
RSpec 3.7
- rspec-core 3.7.1
- rspec-expectations 3.7.0
- rspec-mocks 3.7.0
- rspec-rails 3.7.2
- rspec-support 3.7.1
問題のコード
system spec内にこのようなコードを書いていました。
~略~
visit user_group_path(user_group)
10.times do
break if have_content?("愛島セシル")
sleep 1
visit user_group_path(user_group)
end
expect(page).to have_content("愛島 セシル")
~略~
非同期の処理を行っているページです。
処理が終わったあとページを更新すると結果が反映されます。
目的の文字列が出てくるまでページを更新し、その文字列の存在を確かめるコードです。
have_content?("愛島セシル") で文字列の存在を確かめて、もし文字列が存在すればbreakし、なければ1秒待ってからページを更新します。
次の処理 expect(page).to have_content("愛島 セシル") でページに"愛島 セシル"という文字列があるかどうかテストしています。
このコードがCIで何度も落ちていました。 😱
結論
Rubyはnilとfalseがfalse、それ以外はtrueです。
今回の場合、have_content?("愛島 セシル") により、 #<RSpec::Matchers::BuiltIn::Has:0x00007f8ac84536e8 @args=["愛島 セシル"], @block=nil, @method_name=:have_content?> のオブジェクトが返ってしまっていました。
つまり、if have_content?("愛島 セシル") は true になり、 "愛島 セシル" の描画にかかわらずループを抜けてしまっていましたのです! 👀
ページを更新するコードは一度も読まれていませんでした 😭
詳しく
RSpecの仕様で
have_なんとか と書くと、内部で なんとか? が呼び出されます。
https://github.com/rspec/rspec-expectations/blob/3-8-maintenance/lib/rspec/matchers/built_in/has.rb#L71
def predicate
# On 1.9, there appears to be a bug where String#match can return `false`
# rather than the match data object. Changing to Regex#match appears to
# work around this bug. For an example of this bug, see:
# <https://travis-ci.org/rspec/rspec-expectations/jobs/27549635>
@predicate ||= :"has_#{Matchers::HAS_REGEX.match(@method_name.to_s).captures.first}?"
end
have_content?
pry > have_content?("愛島 セシル")
=> #<RSpec::Matchers::BuiltIn::Has:0x00007f8ac84536e8 @args=["愛島 セシル"], @block=nil, @method_name=:have_content?>
has_content?
pry > has_content?("愛島 セシル")
=> true
# has_text? と一緒
pry > has_text?("愛島 セシル")
=> true
Top comments (0)