Luke Francl
Luke Francl

Reputation: 31464

What is the best way to write specs for code that depends on environment variables?

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.

Upvotes: 88

Views: 36992

Answers (8)

odlp
odlp

Reputation: 5124

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.

Upvotes: 9

Calvin
Calvin

Reputation: 1084

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.

Upvotes: 6

aldrien.h
aldrien.h

Reputation: 3635

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

Upvotes: 10

iGEL
iGEL

Reputation: 17392

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'))

Upvotes: 102

Liam Bennett
Liam Bennett

Reputation: 49

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

Upvotes: 4

Tim Scott
Tim Scott

Reputation: 15205

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.

Upvotes: 2

ciastek
ciastek

Reputation: 1363

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

Upvotes: 8

nicholaides
nicholaides

Reputation: 19489

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.

Upvotes: 29

Related Questions