【问题标题】:RSpec - Using `let` in spec helperRSpec - 在规范助手中使用`let`
【发布时间】:2017-05-28 11:37:18
【问题描述】:

我有一个大型模型,在我的 RSpec 测试中初始化需要时间。

我希望它可能对每个示例可用,但只想在示例需要时才加载它。

这似乎是 let() 延迟加载的完美用途 - 仅在需要时加载。

在我能做的任何特定规范文件中

require "spec_helper"

feature "foo" do
  let(:big_class) { MyBigClass.new(bar) }

  ...
end

这将使 big_class 可用于该规范文件中的每个示例。

有没有办法让它更加全球化,以便每个规范文件和示例都可以使用它?我找不到在规范助手中初始化 let 的好方法。

【问题讨论】:

    标签: ruby-on-rails rspec


    【解决方案1】:

    您可以简单地定义一个shared context 并将其包含在每个示例中。关于您的特定问题,它应该如下所示:

    RSpec.shared_context "Global helpers" do
      let(:big_class) { MyBigClass.new(bar) }
    end
    
    RSpec.configure do |config|
      config.include_context "Global helpers"
    end
    

    但是,在 all 示例中包含共享上下文并不是一个好主意,而且您问题中的 big_class 帮助器确实看起来像是特定于域的东西。您可以通过元数据引导共享上下文包含,例如,当您只想在功能规范中包含给定的共享上下文时(它们都默认设置了 :type => :feature 元数据),您可以这样做:

    RSpec.configure do |config|
      config.include_context "Feature spec helpers", :type => :feature
    end
    

    【讨论】:

      【解决方案2】:

      您可以考虑其他方法:

      • 使用模拟对象而不是真实对象。
      • 重构初始化程序并将缓慢的操作提取到另一个方法

      Mock 对象当然也有自己的缺点;它们可能会变得陈旧并使测试更加脆弱。但对于某些测试来说,这不是问题。

      重构初始化器是我的最爱。例如

      MyBigObject.new(args)
      

      变成

      MyBigObject.new(args).setup
      

      :load_data:connect_to_database_on_the_moon 或任何需要很长时间的东西。你明白了。

      显然这意味着更改您的代码,但我发现这通常会在其他方面有所帮助,而且它肯定会使测试更容易。

      【讨论】:

        【解决方案3】:

        您不想使用let。来自文档:

        使用 let 定义一个记忆化的辅助方法。该值将被缓存 同一示例中的多个调用但不跨示例

        您最终会多次实例化 MyBigClass。我建议在 spec_helper.rb (或类似的)的某个地方创建一个全局帮助方法,如果它已经设置,它自己使用 memo-ization 来返回缓存值。

        还要非常小心所有这些,因为您违反了隔离测试的规则。对你正在做的事情可能没问题,但这是一个危险信号。

        【讨论】:

        • 好吧,我实际上喜欢let() 提供的行为。我不想跨示例缓存 - 每个示例都应该在必要时再次运行它(即懒惰地)。关于单独记忆的辅助方法的好点,只是想知道是否有一种内置方法可以在最高级别的规范(即在 spec_helper 中)定义 let()
        【解决方案4】:

        在一个实例变量上使用全局的 before 钩子

        来自docs

        RSpec.configure do |c|
          c.before(:example) {  @big_class = MyBigClass.new(bar) }
        end
        
        RSpec.describe MyExample do
          it { expect(@big_class).to eql(MyBigClass.new(bar)) } # This code will pass
        end
        

        更多详情请查看answer中的建议

        【讨论】:

        • 是的,但这会在每个示例之前初始化它,无论是否需要。它不像 let() 那样延迟加载
        • 为什么延迟加载会影响您的对象设置时间,您是调用关联还是强制预加载?如果对象那么大,可能是模拟其行为的更好解决方案。
        猜你喜欢
        • 1970-01-01
        • 2018-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多