如何在 MiniTest 中使用假的来测试硬编码 class

How to test a hard coded class using a fake in MiniTest

我有一个 PlantTree 作业调用 PlantTree 服务对象。我想测试该作业以确定它使用 tree 参数实例化 PlantTree 服务并调用 call 方法。

我对服务的作用或结果不感兴趣。它有自己的测试,我不想为这份工作重复这些测试。

# app/jobs/plant_tree_job.rb
class PlantTreeJob < ActiveJob::Base
  def perform(tree)
    PlantTree.new(tree).call
  end
end

# app/services/plant_tree.rb
class PlantTree
  def initialize(tree)
    @tree = tree
  end

  def call
    # Do stuff that plants the tree
  end
end

如您所见,PlantTree class 被硬编码在作业的 perform 方法中。所以我不能伪造它并将其作为依赖项传递。有没有办法在执行方法的生命周期内伪造它?像...

class PlantTreeJobTest < ActiveJob::TestCase
  setup do
    @tree = create(:tree)
  end

  test "instantiates PlantTree service with `@tree` and calls `call`" do
    # Expectation 1: PlantTree will receive `new` with `@tree`
    # Expectation 2: PlatTree will receive `call`
    PlantTreeJob.perform_now(@tree)
    # Verify that expections 1 and 2 happened.
  end
end

我正在使用 Rails' 默认堆栈,它使用 MiniTest。我知道这可以用 Rspec 来完成,但我只对 MiniTest 感兴趣。如果仅使用 MiniTest 或默认的 Rails 堆栈无法做到这一点,我愿意使用外部库。

不确定如何在 Minitest 中编写此代码,但您可以使用模拟(此处为 RSpec 语法):

expect(PlantTree).to receive(:new).with(tree)
expect_any_instance_of(PlantTree).to receive(:call)
# NOTE: either of the above mocks (which are expectations)
# will fail if more than 1 instance receives the method you've mocked -
# that is, PlantTree#new and PlantTree#call
# In RSpec, you can also write this using `receive_message_chain`:
# expect(PlantTree).to receive_message_chain(:new, :call)
job = PlantTreeJob.new(@tree)
job.perform

此测试将失败,除非您的 PlantTree 服务对象 (1) 通过 #new 实例化,并且 (2) 得到 #call 编辑。

免责声明:这可能不是 100% 的功能,但这应该是正确的想法,假设我已经正确阅读了 OP 的 Q。

你应该可以做类似的事情

mock= MiniTest::Mock.new
mock.expect(:call, some_return_value)
PlantTree.stub(:new, -> (t) { assert_equal(tree,t); mock) do
  PlantTreeJob.perform_now(@tree)
end
mock.verify

这会在 PlantTree 上存根新方法,检查树的参数,然后 returns 模拟而不是 PlantTree 实例。该模拟进一步验证调用。

plant_tree_mock= MiniTest::Mock.new
dummy = Object.new
tree = Object.new
plant_tree_mock.expect(:call, dummy, [tree])

PlantTree.stub(:new, plant_tree_mock) do
  PlantTreeJob.perform_now(tree)
end

assert plant_tree_mock.verify