Better Than 'foo'
- 4 minutes read - 683 wordsThere’s almost always a better variable name or value than ‘foo’. It’s useful as a debugging placeholder, but it almost never belongs in production code, even and especially in automated tests.
What is ‘foo’?
The Jargon File calls ‘foo’ a metasyntactic variable: “[a] name used in examples and understood to stand for whatever thing is under discussion, or any random member of a class of things under discussion.”
Two programmers stand at a whiteboard, working on an authentication feature. One says:
“Our customer logs into the site with their email… let’s say it’s ‘foo@bar.com’.”
The precise email doesn’t matter. ‘foo’ tells both programmers that this email is a placeholder.
Where ‘foo’ Fails
A place you see lots of ‘foo’ is automated tests. It makes sense. Almost everything in a test is a kind of placeholder. There is no ‘real’ user with an email and password.
In an integration test, we usually don’t care what a user’s email is, just that it matches a user in the database. So, let’s communicate that by saying their email is ‘foo@bar.com’!
I believe that there’s almost always a better choice. If the user is an admin, their email might be ‘admin@example.com’. This does what ‘foo’ did– tell the user that the email is a throwaway, while also communicating useful context about privileges the user might have.
If there’s more than one user in the test, like an admin and a customer, and we give them ‘admin@example.com’ and ‘customer@example.com’ emails respectively, we’re giving future developers maintaining this test a clearer picture of what’s going on.
And if the variable doesn’t matter, why is it in the test? Automated tests should present relevant information, hiding everything else. In a test that never asserts anything about the email, why not take this:
let(:user) { Factory.build(:user, email: "foo@bar.com" }
And let the factory abstract the irrelevant detail:
let(:user) { Factory.build(:customer) }
The more you use ‘foo’, the less meaning your code has. If ‘foo’ is the value of every string in a test suite, it’s hard to know where some piece of problematic data came from. Was it the test, a factory, or someplace else? It’s all ‘foo’. You’re throwing away context, for no reason other than initial developer speed.
Examples
Here are a few real-world examples where I think ‘foo’ can be improved upon.
Creating an object:
# Bad
let(:vehicle) { build(:vehicle, make: "foo") }
# Better
let(:vehicle) { build(:vehicle, make: "Ford") }
If we’re going to give an attribute to a factory, we’re likely going to assert about it later, like saying that the website is showing the Ford we created. It’s trivial to use something more real-world; why not do that?
Mocking a URL:
# Bad
mocks: {
window: {
location: {
origin: "http://foo"
}
},
}
# Better
mocks: {
window: {
location: {
origin: "http://google.com/search?q=ford"
}
},
}
If we’re asserting that the customer arrived on a page from an external site, why not use one that’s close to a real page they might arrive from, and also a valid URL?
Asserting about behavior:
# Bad
expect(input.text).to eql("ba ba black sheepfoo")
# Better
expect(input.text).to eql("Vehicle color didn't match the photos on the website")
If we’re asserting about user behavior in a form, why not add some context about the kind of company we are and what the customer might type into the form? Let’s seize the chance to model real behavior. Who knows what kind of bugs we might catch, or enhancements we might stumble upon?
Pretty much every use of ‘foo’ I’ve seen in code has an easy, better alternative.
Conclusion
Replacing ‘foo’ is more work, for sure. It’s easier to type ‘foo’ than to imagine something more realistic. In my experience, this makes tests harder to work with over time, and misses valuable communication, branding, bug fixing, and UI opportunities. If the value doesn’t matter, tell me that by abstracting it away.
‘foo’ is part of our history and culture, a badge that you’re in the group. For that purpose, and riffing at a whiteboard, may it continue. But let’s scrutinize it everywhere else.