ruby 在RSpec中使用const_missing定义自定义常量查找

wko9yo5t  于 2023-05-06  发布在  Ruby
关注(0)|答案(1)|浏览(108)

我必须测试一些在Ruby3.2中提供大量 meta编程工具的基类。棘手的部分是,它基于另一个类的常量名称创建方法和工具。这在生产和开发中很好,因为Zeitwerk会自动加载所有依赖项:当缺少MyBaseTooling时,它会查找一个名为my_base_tooling.rb的文件并为我加载该类。
然而,在RSpec中,我必须自己使用stub_const来处理它:

before do
  stub_const(MyBaseTooling, Class.new(Tooling::Base))
  stub_const(MyClass, Class.new(described_class) do
    setup(MyBaseTooling) # parses the name "MyBaseTooling" and does some logic based on the name
  end)
end

当你有一些上下文块并且你覆盖了更深的块中的定义时,这个get非常混乱,非常快:

describe do
  let(:my_base_tooling) do
    stub_const(MyBaseTooling, Class.new(Tooling::Base))
  end
  let(:my_class) do
    my_base_tooling # preload the constant
    stub_const(MyClass, Class.new(described_class) do
      setup(MyBaseTooling) # parses the name "MyBaseTooling" and does some logic based on the name
    end)
  end

  context "with more logic in my base tooling" do
    let(:my_base_tooling) do
      stub_const(MyBaseTooling, Class.new(Tooling::Base) do
        # more custom logic
      end)
    end
  end
end

我不喜欢的部分是通过在let(:my_class)中调用my_base_tooling的let来手动加载MyBaseTooling。想象一下,你不仅仅有2个类,你有8个或更多的类(这是我目前的情况)。
所以我想,也许我可以为RSpec提供一个自定义的查找逻辑,当一个常量丢失时,就像Zeitwerk一样。但我不是查找文件,而是查找具有相同命名约定的let帮助程序。到目前为止,我的想法是:

  • 使用const_missing(https://devdocs.io/ruby~3.2/module#method-i-const_missing)
  • 当一个常量丢失时,执行一个name.underscore,并查看是否用这个名称定义了一个let;如果是,则返回此值

我玩了const_missing钩子,但到目前为止还没有任何运气。有什么想法可以做到这一点,或者这是否可能?

5anewei6

5anewei61#

看看你的测试代码,我认为你需要做的就是使用let!,像这样:

describe do
  let!(:my_base_tooling) do
    stub_const(MyBaseTooling, Class.new(Tooling::Base))
  end

  let(:my_class) do
    stub_const(MyClass, Class.new(described_class) do
      setup(MyBaseTooling)
    end)
  end

  context "with more logic in my base tooling" do
    let!(:my_base_tooling) do
      stub_const(MyBaseTooling, Class.new(Tooling::Base) do
        # more custom logic
      end)
    end
  end
end

通常,RSpec会延迟加载let语句。换句话说,它会一直等待,直到您引用let定义的常量,以执行与let语句关联的代码块。
通过将bang(!)添加到let语句中,您告诉RSpec立即执行块中的代码。这具有立即将值赋给变量的效果。这样,就不再需要在my_class let语句中调用my_base_toolingMyBaseTooling将已被存根化。
请注意,我在my_base_tooling的两个定义上都调用了let!。您需要这样做,否则当调用my_class时,第一个定义将出现,但context中的第二个定义将不存在。

相关问题