Setting Constants While Testing
Let’s suppose you’ve got behavior in your app which is dependent on how RAILS_ENV is set and — of course — you want to test that it’s behaving properly. To do this, you need to change the value of RAILS_ENV just before you call that function (and change it back just after). This puzzled me for a while, but I finally figured it out:
class FooTest << Test::Unit::TestCase
def test_doing_the_thing_with_the_stuff_in_production
foo = foos(:the_one_to_test)
orig_rails_env = RAILS_ENV
suspend_warnings {Foo.const_set(:RAILS_ENV, 'production')}
assert foo.do_the_thing(:with => the_stuff)
suspend_warnings {Foo.const_set(:RAILS_ENV, orig_rails_env)}
end
end
( const_set comes from Ruby’s Module class, which also provides const_get, const_defined?, and const_missing. Nifty!)
That was all well and good for my unit tests, but what about functionals tests? That turned out to be a slight variation on the theme:
class FooControllerTest << Test::Unit::TestCase
def test_getting_the_thing_with_the_stuff_in_production
foo = foos(:the_one_to_test)
orig_rails_env = RAILS_ENV
suspend_warnings {FooController.const_set(:RAILS_ENV, 'production')}
get :the_thing_with_the_stuff
assert :success
suspend_warnings {FooController.const_set(:RAILS_ENV, orig_rails_env)}
end
end
You’d think that was enough, but noooo, I’ve even got a function in my ApplicationController which not only behaves differently per RAILS_ENV but also honors a constant (call it FOOISH) which may be set differently (or not set) in each environment. Holy crap! Dealing with that’s a bit painful since I don’t want to hardcode FOOISH’s current per-environment settings in my tests. Then I tripped over a neat bit of code in config/boot.rb and mutated it into this test helper:
def get_var_from_rails_environment(varname, env = RAILS_ENV)
global_env_file = File.join(RAILS_ROOT, 'config', 'environment.rb')
specific_env_file = File.join(RAILS_ROOT, 'config', 'environments', "#{env}.rb")
[specific_env_file, global_env_file].each do |env_file|
IO.readlines(env_file).grep(/^\s*#{varname}\s*=\s*(['"]?)(.*)\1/)
break if $2
end
$2
end
This lets me make it clean and easy:
def test_getting_the_thing_with_the_stuff_fooishly
orig_fooish = ApplicationController.const_get(:FOOISH)
orig_rails_env = ApplicationController.const_get(:RAILS_ENV)
%w(development staging test production).each do |rails_env|
fooish = get_var_from_rails_environment('FOOISH', rails_env)
next unless fooish
silence_warnings {
ApplicationController.const_set(:FOOISH, fooish)
ApplicationController.const_set(:RAILS_ENV, rails_env)
}
get :the_thing_with_the_stuff
silence_warnings {
ApplicationController.const_set(:FOOISH, orig_fooish)
ApplicationController.const_set(:RAILS_ENV, orig_rails_env)
}
end
end
That’s as elegant as I can make it. Not a bad day’s work.
Update: Ryan Bates rightly advises caution in changing RAILS_ENV as you might trigger subtle side effects. Proceed with caution.
blog comments powered by Disqus