Avoid test specific code
Don’t write code that checks the environment. It’s a recipe for disaster.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
def security return false if (Rails.env.test? || Rails.env.cucumber?) really_complicated_method end private def really_complicated_method fail "Oops" end |
In the above example, you are screwed because your entire suite will pass. But then, once you cap deploy, everything will crash and burn.
Checking for the environment is a code smell.
In my experience, code like above causes bugs ranging from small but insidious to outright disasters.
Ways to ensure environment agnosticism
There are cases where being entirely environment-agnostic this doesn’t always make sense. I would argue that these cases should only be when you have to hit an external API that will either significantly slow down your test suite or otherwise be annoying, for example if it cost $2/request or something.
In these cases, I recommend mocking out the service in a class included in the environment file. You can then set the actual service to be a constant. This allows you to turn the mocking on and off.
You turn it off when you want to avoid hitting an API in your tests, and you turn it on when you want to make sure that everything works.
Mocking a payment gateway
Here’s an example of how you might do this for a subscription based site. Note that all you have to do to test the live API is comment out the mock, provided you’ve defined it elsewhere. This is a far better than having environment checks hidden everywhere in your code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# config/environments/cucumber.rb require 'spec/mocks' @recurring = Spec::Mocks::Mock.new("Recurring Response", :params => {"subscription_id" => 12323231}, :success? => true, :authorization => '232323', :message => 'message string' ) @purchase = Spec::Mocks::Mock.new("Immediate Response", :success? => true, :authorization => "12143242", :message => "message string", :params => {}) GATEWAY = Spec::Mocks::Mock.new("Gateway", :purchase => @purchase, :recurring => @recurring, :cancel_recurring => true) |
At the same time, I really try to avoid doing things like this. I think that environment constants are also a code smell, since they often unnecessarily obfuscate what’s going on. In general, it’s best to just keep production, development, and test as closely aligned as possible to avoid issues that slip past tests.
January 17, 2010

