Site logo
Published on

A nasty `ruby` gotcha for node.js developers

Authors

A difference in how ruby and node.js treat backticks presents a tempting pitfall for developers writing ruby tests.

TL;DR: If you need to preserve line breaks in a ruby string, use a squiggly <<~HEREDOC.strip. If you are trying to test around annoying line breaks in Rails, try String#squish.

Developers working in the Node.js/ECMAScript universe are familiar with the language's template string literals. One feature that has made them so popular, even when string interpolation is not needed, is the ability to preserve line breaks:

const result = 'Your order\nis ready'
const expected = `You're order
is ready`

result.includes(expected)
// => false

In ruby however, backticks are a shorthand for running a script in a shell and returning its output to STDOUT as a string:

my_files = `ls ~`
# => "Applications\nDesktop\nDocuments\nDownloads\n ...

If the script errors, the backticked method exits and returns nil, but adding a line break obscures this behavior, returning an empty string (nothing was sent to STDOUT) instead:

x = `Your order`
# Errno::ENOENT: No such file or directory - Your
x
# => nil

expected = `Your order
is ready`
# sh: Your: command not found
# sh: line 1: is: command not found
# => ""

If you are accustomed to using this backticked behavior to test strings with line breaks, these quirks can combine to hide false positives in tests:

expected = `Your order
is ready`

"You're order\nis ready".include?(expected)
# => true

Testing multiline strings in ruby

Ruby's (uglier, more awkward) method for a case like this is to use its HEREDOC format:

expected = <<~HERE
  Your order
  is ready
HERE
# => "Your order\nis ready\n"

# Again without the final newline:
expected = <<~HERE.strip
  Your order
  is ready
HERE
# => "Your order\nis ready"

# Finally our test is working
"You're order\nis ready".include?(expected)
# => false

Depending on your testing needs, there are other ways to get around this. In rails for example String#squish will convert all successive whitespaces in a string to a single space.

actual = "Your order\nis ready"
expected = "Your order is ready"

test_subject = actual.squish

test_subject.include?(expected)
# => true