DEV Community

risacan
risacan

Posted on

RSpecの have_content? と has_content? は違う

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はnilfalseが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)