That‘s Not In My String!

That‘s Not In My String!

Colin Jones
Colin Jones

February 02, 2011

We encountered a strange spec failure today in our continuous integration build. It’s a Rails project, we’re using RSpec, and this was the failing expectation in the controller spec:

response.body.should_not include(@thing.id)

Have you already spotted the problem? Great job if so! But for the rest of us, here’s what’s going on.

RSpec’s include matcher calls the include? instance method on the response body, which is an instance of String. No problems here! String#include? is a perfectly reasonable thing to be testing.

The problem is the argument to include? (@thing) is an ActiveRecord object, so we know that @thing.id is going to be a Fixnum. And it was—the failure message told us that @thing.id was 16812882.

This should be starting to sound fishy. If we look at the RubyDocs, we see that String#include? “Returns true if str contains the given string or character.” Okay, this isn’t saying anything about allowing a Fixnum as input, but maybe it’s treating the Fixnum as a character in this case?

Well, it turns out that’s exactly what’s happening. It took a few minutes longer to figure out what 16812882 was a character code for, but it turns out that Ruby just truncates to the least significant 8 bits.

So since 16812882 % 256 is 82, we’re left with Ruby looking for 82.chr ("R") in the given string. And because response.body included the string "RJS", 16812882 could be found in the string, and so our test failed.

The lessons here? On a micro level, make sure you’re passing String arguments to String#include?, or make looking for a character explicit, with something like ?R. And stepping back a bit, remember that not every method or function in the world checks for bad input and raises exceptions to stop you. Knowing your tools includes knowing their expectations about how you use them.