【问题标题】:RSpec: Accessing a `let` definition from within a block in an exampleRSpec:在示例中从块中访问“let”定义
【发布时间】:2020-11-26 14:24:06
【问题描述】:

我在 RSpec 测试中基本上有这个设置:

RSpec.describe Something do
  describe '#something' do
    let(:my_var) { 'some data' }
    let(:my_object) { MyObject.new }
    it 'example' do
      my_object.instance_eval { @my_inst_var = my_var } # ERROR here
    end
  end
end

这会导致

NameError: #MyObject:0x5d14e99e 的未定义局部变量或方法“myvar”

我了解,在my_object 的执行上下文中,Ruby 不知道如何处理 RSpec 中 let 构造的“魔法”。我知道我可以在这里使用“普通变量”而不是 let,我也可以这样做(这就是我目前解决问题的方式):

    # This works of course
    it 'example' do
      mv = my_var # Forces the let(:my_var) to be evaluated
      my_object.instance_eval { @my_inst_var = mv }
    end

问题:假设我确实想在示例中使用let 和`instance_eval,有没有更好的方法?

【问题讨论】:

  • "我知道,在 my_object 的执行上下文中,Ruby 不知道如何处理 RSpec 中 let 构造的“魔法”。" ——没有魔法。 let(:foo) 只是定义了一个名为foo 的方法,仅此而已。 instance_eval全部意义 是更改 self 以便在其他地方查找方法,所以很明显,foo 不存在。这就是为什么即使是过去大量使用它的社区(例如 Rails)也几乎禁止使用它的许多原因之一:因为那些看起来应该是在范围内,不是。

标签: ruby rspec


【解决方案1】:

改用instance_exec - 它允许您将参数传递到块的范围:

my_object.instance_exec(my_var) { |v| @my_inst_var = v }

或者,您可以通过instance_variable_set 设置实例变量:

my_object.instance_variable_set(:@my_inst_var, my_var)

尽管上述两种方法都有效,但以这种方式改变对象的状态可能会导致脆弱的测试。您应该考虑更改对象,以便更容易测试。 (在初始化时添加一个setter或传递值)

【讨论】:

    【解决方案2】:

    有不同的方法可以做到这一点。使用 instance_eval 在我的列表中并不高。 如果 attr_accessor 有意义,您肯定应该这样做:

    class Something
      attr_accessor :my_inst_var
    end
    
    it 'example' do
      my_object.my_inst_var = my_var
    end
    

    如果您不想污染对象的公共方法,我会考虑以下选项比 instance_eval 更清洁。

    it 'example' do
      my_object.instance_variable_set :@my_instance_var, my_var
    end
    

    【讨论】:

    • 我不想把我的attr_reader 变成attr_accessor 只是为了让编写测试用例更容易,但是使用instance_variable_set 对我的具体案例来说很好。几天后我会接受你的回答,除非在那之前有人提出更通用的解决方案。
    • @user1934428 可测试性 > 封装
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多