Do you know which tests are going to pass and which tests are going to fail in this test file?
require "minitest/autorun"
class TestBlocks < Minitest::Test
def setup
@values = ["a", "b", "c", "d"]
end
def test_passing_a_block_with_do
assert @values.all? do |value|
false
end
end
def test_passing_a_block_with_do_surround_by_parentheses
assert(@values.all? do |value|
false
end)
end
def test_passing_a_block_with_braces
assert @values.all? { |value| false }
end
end
How do you do in your code base? If you do like in test_passing_a_block_with_do it is bad news.
test_passing_a_block_with_do is the only one which is going to pass, but why? I know you know, assert receives @values.all? do |value| as a block, then it thinks this block is true and that’s it. If you thought that, you’re almost right. Let’s see what is assert.
# https://github.com/seattlerb/minitest/blob/6257210b7accfeb218b4388aaa36d3d45c5c41a5/lib/minitest/assertions.rb#L178
def assert test, msg = nil
self.assertions += 1
unless test then
msg ||= "Expected #{mu_pp test} to be truthy."
msg = msg.call if Proc === msg
raise Minitest::Assertion, msg
end
true
end
So, is test a Proc? What do you think?
After debugging this method I know that test is true. Why is it true? The class is TrueClass, so it is a proper true. What is happening here?
What is msg then? msg is the Proc then, right?
(byebug) msg
nil
msg is nil. Okay, it got the default value. Where is my block then? Well, there is only one place left now.
(byebug) yield
false
Ok, false. The block I’m passing is returning false, is that the reason? If I change to:
def test_passing_a_block_with_do
assert @values.all? do |value|
"hello, world!"
end
end
Then I have:
(byebug) yield
"hello, world!"
Ahhhh, that was it.
A mindful programmer would figure that out at first saw, there is no reason to think that the whole collection.all? do...end is going to be a single argument. However, I think it’s pretty easy to miss that, have you got it at first glance?