ruby-on-railstestingmockingminiteststubs

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


I have a PlantTree job that calls a PlantTree service object. I would like to test the job to ascertain that it instantiates the PlantTree service with a tree argument and calls the call method.

I'm not interested in what the service does or the result. It has its own tests, and I don't want to repeat those tests for the job.

# 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

As you can see, the PlantTree class is hard coded in the perform method of the job. So I can't fake it and pass it in as a dependency. Is there a way I can fake it for the life time of the perform method? Something like...

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

I'm using Rails' default stack, which uses MiniTest. I know this can be done with Rspec, but I'm only interested in MiniTest. If it's not possible to do this with MiniTest only, or the default Rails stack, I'm open to using an external library.


Solution

  • You should be able to do something like

    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
    

    This stubs the new method on PlantTree, checks the argument to tree and then returns a mock instead of a PlantTree instance. That mock further verifies that call was called.