8

今日、解決したカスタム RSpec マッチャーの定義に関する問題がありましたが、実際には、アプローチの 1 つが機能し、もう 1 つが機能しない理由の背後にある理由を理解できませんでした。コードは次のとおりです。

アプローチ 1 -- if + else:

RSpec::Matchers.define :have_success_message do |message|
  match do |page|
    if message.nil?
      page.should have_selector('div.alert.alert-success')
    else
      page.should have_selector('div.alert.alert-success', text: message)
    end
  end
end

アプローチ 2 -- if が続く場合

RSpec::Matchers.define :have_success_message do |message|
  match do |page|
    page.should have_selector('div.alert.alert-success') if message.nil?
    page.should have_selector('div.alert.alert-success', text: message) unless message.nil?
  end
end

条件を1回だけチェックするため、最初のアプローチの方が優れていると思いますが、それでも結果は同じはずですよね?

さて、最初のアプローチのテストはパスしますが、2 番目のアプローチのテストはパスしません。それがなぜなのか、私は完全に無知であり、誰かがそれに光を当てることができれば幸いです.

編集:

実際のテストを追加するのを忘れました(アプローチ2を使用):

次の HTML タグが存在する場合:

<div class="alert alert-success">Profile updated</div>

4 つの個別のテストを実行します。

it { should have_success_message } # fails
it { should have_success_message('Profile updated') } # passes
it { should have_selector('div.alert.alert-success') } # passes
it { should have_selector('div.alert.alert-success', text: "Profile updated") } # passes

失敗は次のメッセージです。

1) User pages edit with valid information 
 Failure/Error: it { should have_success_message }
   expected #<Capybara::Session> to have success message
 # ./spec/requests/user_pages_spec.rb:80:in `block (5 levels) in <top (required)>'

HTML タグが存在しない場合、4 つのテストすべてが失敗します。

編集2:

制御フローが正しいかどうかを確認する別の方法を試しました。

アプローチ 3:

if message.nil?
  puts "In if, message is: #{message.inspect}"
  page.should(have_selector('div.alert.alert-success'))
end
unless message.nil?
  puts "In unless, message is: #{message.inspect}"
  page.should(have_selector('div.alert.alert-success', text: message))
end

このアプローチでは、動作はアプローチ 2 と同じです - 最初のテストは失敗し、3 回のパスに続きます。

出力は次のとおりです。

if の場合のメッセージ: nil で
ない場合のメッセージ: "プロファイルが更新されました"

制御フローは問題ないように見えますが、

page.should(have_selector('div.alert.alert-success'))

マッチャーの外を通過しても失敗します。これは本当に謎です。

最終編集:

承認された回答に応じて-次のようにコードを切り替えると:

page.should have_selector('div.alert.alert-success', text: message) unless message.nil? 
page.should have_selector('div.alert.alert-success') if message.nil?

テストは次のようになります。

it { should have_success_message } # passes
it { should have_success_message('Profile updated') } # fails
it { should have_selector('div.alert.alert-success') } # passes
it { should have_selector('div.alert.alert-success', text: "Profile updated") } # passes

したがって、最後の行が真でない場合、実際には nil と評価され、それが全体の混乱を引き起こすと思います。とにかく最初のアプローチの方が優れていますが、この問題が頭から離れてうれしいです:)

4

2 に答える 2

6

予想外のように見えますが、これは RSpec の正しい動作です。

次のコードを検討してください。

x = nil
"foo" if x.nil?
"bar" unless x.nil?
#=> 
"foo"
nil

条件が false の場合、ステートメント...unlessは戻ります。nil

カスタムマッチャーでは...unless、メッセージが nil の場合、ステートメントは nil を返します。

これは match ブロックの最後の行なので、match ブロックは nil を返します。

その後、RSpec は match ブロックが nil を返したことを確認します。これは RSpec が false と同じと見なすため、RSpec はカスタム マッチャーが失敗したことを報告します。

于 2012-12-01T20:48:46.297 に答える
3

うわー、それはきちんとしたパズルでした!重要なのは、matchメソッドがブール値の結果を返すことが期待されていることです。最初のオプションでは、暗黙的な戻り値は、if分岐の結果である ですtrue

なぜtrueですか?shouldは、次のように定義されています。

      def should(matcher=nil, message=nil, &block)
        ::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
      end

に委譲しPositiveExpectationHandler、そのhandle_matcherメソッドは次のようになります。

  def self.handle_matcher(actual, matcher, message=nil, &block)
    check_message(message)
    ::RSpec::Matchers.last_should = :should
    ::RSpec::Matchers.last_matcher = matcher
    return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) if matcher.nil?

    match = matcher.matches?(actual, &block)
    return match if match

    message ||= matcher.respond_to?(:failure_message_for_should) ?
                matcher.failure_message_for_should :
                matcher.failure_message

    if matcher.respond_to?(:diffable?) && matcher.diffable?
      ::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
    else
      ::RSpec::Expectations.fail_with message
    end
  end

マッチャーがtrue(または実際には任意の真の値) を返す場合、 this が関数から返され、暗黙的に の戻り値になることがわかりますshould。この動作がどこかに文書化されているかどうかはわかりません。

ただし、2 番目のオプションでは、最後の式/ステートメントの値が優先され、unless条件がtrueであるため、式は次のように評価されnilます。

1.9.3-p0 :001 > x unless true
 => nil 

したがって、RSpec は、nil誤って戻ってきたため、マッチャーが失敗を報告しようとしていると考えます。


これを修正するには、おそらくマッチャーで使用するべきではありませんshould。次のように、Capybara のHaveSelectorマッチャーを呼び出すことができます。

if message.nil?
  have_selector('div.alert.alert-success').matches? page
else
  have_selector('div.alert.alert-success', text: message).matches? page
end

ちなみに、text: nil最終的に指定すると、空の正規表現になるここまで細流化されるため、チェックnilはとにかく必要なく、次のようにマッチャーを記述できます。

match do |page|
  have_selector('div.alert.alert-success', text: message).matches? page
end

あまりRubyesqueではないことは認めますが、まあまあです。

于 2012-12-01T22:05:21.027 に答える