我今天遇到了一个问题,我定义了自定义的RSpec匹配器,但我实际上看不出为什么其中一种方法有效,而另一种方法无效,这就是代码:
方法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 - 如果后面跟着
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
我认为第一种方法更好,因为它只检查一次条件,但是,结果应该是相同的,对吗?
嗯,事实证明第一种方法的测试通过了,而第二种方法的测试则没有。我完全不知道为什么会这样,并且如果有人能够对此有所了解,我会很高兴。
编辑:
忘记添加实际测试(使用方法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次传递。
输出如下:
如果,消息是: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
所以我认为确实最后一行,当它不是真的时,被评估为零,这导致整个混乱。无论如何,第一种方法更好,但我很高兴我有这个问题:)
答案 0 :(得分:6)
这是RSpec的正确行为,即使看起来很意外。
考虑以下代码:
x = nil
"foo" if x.nil?
"bar" unless x.nil?
#=>
"foo"
nil
条件为false时,...unless
语句返回nil
。
在您的自定义匹配器中,当您的邮件为零时,...unless
语句将返回nil。
这是你的匹配区中的最后一行,所以你的匹配区块返回nil。
然后RSpec看到你的匹配块返回nil,RSpec认为它与false相同,因此RSpec报告你的自定义匹配器失败。
答案 1 :(得分:3)
match
方法应该返回一个布尔结果。在第一个选项中,隐式返回值是if
分支的结果,即true
。
为什么true
?好吧,should
是这样的defined:
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
(或实际上任何真值),则从函数返回,并隐式变为should
的返回值。我不确定这种行为是否记录在任何地方。
但是,在第二个选项中,最后一个表达式/语句的值获胜,并且因为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
最终会渗透到here,这会导致空的正则表达式,因此无论如何都不需要检查nil
,你可以像这样编写匹配器:
match do |page|
have_selector('div.alert.alert-success', text: message).matches? page
end
不是很像Rubyesque,我承认,但你去了。