83

I am testing some code that pulls its configuration from environment variables (set by Heroku config vars in production, for local development I use foreman).

What's the best way to test this kind of code with RSpec?

I came up with this:

before :each do
    ENV.stub(:[]).with("AWS_ACCESS_KEY_ID").and_return("asdf")
    ENV.stub(:[]).with("AWS_SECRET_ACCESS_KEY").and_return("secret")
end

If you don't need to test different values of the environment variables, I guess you could set them in spec_helper instead.

Luke Francl
  • 31,028
  • 18
  • 69
  • 91

8 Answers8

95

You also can stub the constant:

stub_const('ENV', {'AWS_ACCESS_KEY_ID' => 'asdf'})

Or, if you still want the rest of the ENV:

stub_const('ENV', ENV.to_hash.merge('AWS_ACCESS_KEY_ID' => 'asdf'))
Fabio
  • 18,856
  • 9
  • 82
  • 114
iGEL
  • 16,540
  • 11
  • 60
  • 74
  • 3
    `ENV` doesn't appear to have a `merge` method (at least in Ruby 1.9.3). – David Tuite Aug 27 '14 at 12:26
  • 1
    This is the best approach for a quick env stubs, but in second case you need `ENV.to_hash` to preserve original env. – Fabio Nov 17 '14 at 13:51
  • Making the hash keys double quoted made it work for me. When i had the keys with single quotes, they got converted to :symbols. Ex: :AWS_ACCESS_KEY_ID – Pratik Khadloya Sep 15 '15 at 05:57
29

That would work.

Another way would be to put a layer of indirection between your code and the environment variables, like some sort of configuration object that's easy to mock.

nicholaides
  • 19,211
  • 12
  • 66
  • 82
  • 2
    If this is in Rails you already have its own configuration object to add to if you want. – Andrew Marshall Mar 08 '12 at 00:49
  • 3
    That is a handy tip. I did not know you could define your own configuration options so easily. Just do this in environments/*.rb: `config.my_config_value = 'value'` and it will be made available as `Rails.configuration.my_config_value`. – Luke Francl Mar 20 '12 at 16:07
10

This syntax works for me:

module SetEnvVariable

  def set_env_var(name, value)
   # Old Syntax
   # ENV.stub(:[])
   # ENV.stub(:[]).with(name).and_return(value)

   allow(ENV).to receive(:[]) # stub a default value first if message might be received with other args as well.
   allow(ENV).to receive(:[]).with(name).and_return(value)
  end

end
aldrien.h
  • 3,437
  • 2
  • 30
  • 52
8

As Heroku suggests, you can use Foreman's .env file to store environment variables for development.

If you do that, you can use foreman run to run your specs:

foreman run bundle exec rspec spec
ciastek
  • 1,343
  • 12
  • 15
8

If you're using dotenv to setup your environment during tests but need to modify an env variable for a specific test then following approach can be useful.

A simpler method than stubbing ENV is to replace the environment for the duration of the test, and then restore it afterwards like so:

with_environment("FOO" => "baz") do
  puts ENV.fetch("FOO")
end

Using a helper like this:

module EnvironmentHelper
  def with_environment(replacement_env)
    original_env = ENV.to_hash
    ENV.update(replacement_env)

    yield
  ensure
    ENV.replace(original_env)
  end
end

By using ensure the original environment is restored even if the test fails.

There's a handy comparison of methods for setting & modifying environment variables during tests including stubbing the ENV, replacing values before / after the test, and gems like ClimateControl.

odlp
  • 4,984
  • 2
  • 34
  • 45
4

I'd avoid ENV.stub(:[]) - it does not work if other things are using ENV such as pry(you'll get an error about needing to stub DISABLE_PRY).

#stub_const works well as already pointed out.

Viktor
  • 2,623
  • 3
  • 19
  • 28
Calvin
  • 1,056
  • 8
  • 5
4

You can use https://github.com/littleowllabs/stub_env to achieve this. It allows you to stub individual environment variables without stubbing all of them as your solution suggested.

Install the gem then write

before :each do
  stub_env('AWS_ACCESS_KEY_ID', 'asdf')
  stub_env('AWS_SECRET_ACCESS_KEY','secret')
end
1

What you want is the dotenv gem.

Running tests under foreman, as @ciastek suggests, works great when running specs from CLI. But that doesn't help me run specs with Ruby Test in Sublime Text 2. Dotenv does exactly what you, transparently.

Tim Scott
  • 15,106
  • 9
  • 65
  • 79