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

Published

30 September 2007

Categories

ruby/rails testing