简体   繁体   中英

how can I change environment variables when running rspec for ruby?

I have several ruby scripts and test them with rspec.

I put my environments in a env.rb file (for now) so I can access them locally and in production put them in the config variables.

But when I run rspec, I would like different environment variables. Two use cases:

  • I run Twilio, so I want to be able to change the SID used to their Test Credentials
  • I store things in a database as a service, and want to have a separate test database

You can

  • set ENV vars explicitly in the context with ENV["FOO_BAR"] = "baz"
  • check Rails.env.test? in your initializers to setup twilio and other clients with test specific opts
  • have a file with all your env setup for test and then use dotenv

I personally prefer to use ENV vars only when creating objects and then passing the env var value to the constructor, so I can test the object class without caring about ENV, and I can test the object that initializes the other object with the env var, by just asserting the creation used the env var.

So you'd change something like

class Client
  def initialize
    @restclient = RestClient::Resource.new(ENV["API_URL"])
  end
end

to

class Client
  def initialize(url)
    @restclient = RestClient::Resource.new(url)
  end
end

and have whatever is initializing that instance to then pass the value of the env var

def fetch_content
  client = Client.new(ENV["API_URL"])
  # ...
end

this way you can test the Client class without caring about the env var by just passing any url, and then can test the class that instantiates the client as

it "uses client" do
  ENV["API_URL"] = "foo.com"
  expect(Client).to receive(:new).with(ENV["API_URL"])  
  subject.fetch_content
end

one problem with updating the env var is that the change persist throughout the rest of the test suite, and may cause problems if you don't expect it to be there in some tests, in these cases you can mock the value with

expect(ENV).to receive(:[]).with("API_URL").and_return("foo.com")

Since ENV is global state you want to reset the value after each spec to avoid it leaking between specs:

describe 'enabled?' do
  around do |example|
    env_value_before = ENV['MIXPANEL_ENABLED']
    ENV['MIXPANEL_ENABLED'] = env_value
    example.run
    ENV['MIXPANEL_ENABLED'] = env_value_before
  end

  context 'when ENV not set' do
    let(:env_value) { nil }

    it 'returns true' do
      expect(subject.enabled?).to eq(true)
    end
  end

  context 'when ENV is set' do
    let(:env_value) { '1' }

    it 'returns false' do
      expect(subject.enabled?).to eq(false)
    end
  end
end

You can mock ENV to return a value. This is a super-clean one-liner that ensures that nothing can leak out to other tests:

context 'when LOG_LEVEL is DEBUG' do
  before { allow(ENV).to receive(:[]).with('LOG_LEVEL').and_return('debug') }
  ...

That works great if your code only looks at one ENV var. If it needs other vars that are not of interest to this test, you can allow those to call original: (although this starts to feel a bit cludgy)

context 'when LOG_LEVEL is DEBUG' do
  before do
    allow(ENV).to receive(:[]).and_call_original
    allow(ENV).to receive(:[]).with('LOG_LEVEL').and_return('debug')
  end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM