【问题标题】:Rspec: mocked ENV vars are visible in examples, but not in application codeRspec:模拟的 ENV 变量在示例中可见,但在应用程序代码中不可见
【发布时间】:2021-05-09 10:43:10
【问题描述】:

TL;DR - 我的 ruby​​ 类可以读取现实生活中的 ENV 变量。 Rspec 示例可以读取模拟的 ENV var。但是我的 ruby​​ 类在测试中无法读取相同的模拟 ENV var。我做错了什么?

全文:

我有这个 ruby​​ 类,它使用(可选)ENV var 来设置用户的风格,默认为“香草”:

class MyConfigger
  attr_reader :flavor

  def load_config
    @flavor = ENV['MY_FLAVOR'] || 'vanilla'
    self
  end
end

这很有效,在 IRB 中测试过:

% irb -I lib -r my_configger
irb(main):001:0> MyConfigger.new.load_config.flavor
=> "vanilla"


% MY_FLAVOR=cherry irb -I lib -r my_configger
irb(main):001:0> MyConfigger.new.load_config.flavor
=> "cherry"

但是,当我对其运行测试时,它看不到模拟的 ENV var。前两个按预期通过,但最后一个失败,表明我的应用代码没有看到模拟的 ENV var:

RSpec.describe MyConfigger do
  let(:config) { described_class.new }

  before { config.load_config }

  describe '.flavor' do
    subject { config.flavor }

    context 'with defaults' do
      it { is_expected.to eq 'vanilla' }
    end

    context 'when MY_ENV=chocolate' do
      before { allow(ENV).to receive(:[]).with('MY_FLAVOR').and_return('chocolate') }

      it "ENV['MY_FLAVOR'] in example is chocolate" do # This test passes.
        expect(ENV['MY_FLAVOR']).to eq 'chocolate'
      end

      it 'config.flavor is chocolate' do  #  <<---------------- THIS TEST FAILS
        is_expected.to eq 'chocolate'
      end
    end
  end
end
MyConfigger
  .flavor
    with defaults
      is expected to eq "vanilla"
    when MY_ENV=chocolate
      ENV['MY_FLAVOR'] in example is chocolate
      config.flavor is chocolate (FAILED - 1)

Failures:

  1) MyConfigger.flavor when MY_ENV=chocolate config.flavor is chocolate
     Failure/Error: is_expected.to eq 'chocolate'

       expected: "chocolate"
            got: "vanilla"

       (compared using ==)
     # ./spec/lib/my_configger_fail_spec.rb:25:in `block (4 levels) in <top (required)>'

3 examples, 1 failure

我尝试了 许多 ENV['MY_FLAVOR']ENV.fetch('MY_FLAVOR', 'vanilla') 等的变体,但没有一个成功。

我错过了什么?

【问题讨论】:

    标签: ruby rspec mocking environment-variables


    【解决方案1】:

    这个问题与 ENV 变量无关,与模拟无关。这真的归结为 rspec 中 before 块的执行顺序。

    经过多次拉扯并要求同事提供第二双眼睛后,我们意识到before 深层中的 allow(ENV)... 模拟是在before 块之后执行在测试套件的顶部。

    这有点违反直觉(至少对我而言),因为我们已经习惯了深入的 let 分配覆盖上层 let 分配。

    最后,我将allow 命令移到了初始before 块,以确保它在load_config 被调用之前运行。 (这需要为模拟值使用一个变量,因此它不会破坏默认 'vanilla' 值的第一次测试。)

    RSpec.describe MyConfigger do
      let(:config) { described_class.new }
      let(:flavor) { nil }
    
      before {
        allow(ENV).to receive(:[]).with('MY_FLAVOR').and_return(flavor) #   <<------ MOVED UP HERE
        config.load_config
      }
    
      describe '.flavor' do
        subject { config.flavor }
    
        context 'with defaults' do
          it { is_expected.to eq 'vanilla' }
        end
    
        context 'when MY_ENV=chocolate' do
          let(:flavor) { 'chocolate' }
    
          it "ENV['MY_FLAVOR'] in example is chocolate" do 
            expect(ENV['MY_FLAVOR']).to eq 'chocolate'
          end
    
          it 'config.flavor is chocolate' do  # This test now passes! :-)
            is_expected.to eq 'chocolate'
          end
        end
      end
    end
    

    现在,我有快乐的测试:

    MyConfigger
      .flavor
        with defaults
          is expected to eq "vanilla"
        when MY_ENV=chocolate
          ENV['MY_FLAVOR'] in example is chocolate
          config.flavor is chocolate
    
    3 examples, 0 failures
    

    【讨论】:

    • Before(:each) 块和 let 分配通常都按照它们遇到的顺序执行。排序没有区别;只是顺序运行的块通常会产生副作用,可能会影响其他上下文或绕过范围门,而顺序分配(简单的#let 语句通常类似于)通常不会。
    • 您可能对before 块的顺序是正确的,但let 分配绝对是分层的。也就是说,更深层次的let 分配将始终覆盖更高级别的let 分配。这可能与您的断言非常吻合,即它们在很多情况下都是按照遇到的顺序执行的,但并不能说明全部情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-15
    • 2017-04-16
    • 1970-01-01
    • 1970-01-01
    • 2015-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多