Androidgrl's Blog About 3D Printing & Tech, by Jamie Kawahara

Determining the Scope of What to Test Inside Methods

Sometimes it can be difficult to determine what you want to test inside a method, especially if that method calls other methods, which in turn call other methods and so on. For example, take the example class Cake below. It has a method called make which calls two methods mix_ingredients and bake, which in turn call Foo.mix and Foo.bake respectively.

class Cake
  def make
    mix_ingredients
    bake
  end

  def mix_ingredients
    Foo.mix
  end

  private

  def bake
    Foo.bake
  end
end

class Foo
  def self.mix
  end

  def self.bake
  end
end

If we were to write a test for make, certainly we would want to test that mix_ingredients and bake get called.

describe Cake do
  describe '#make' do
    it 'calls mix_ingredients and bake' do
      expect_any_instance_of(described_class).to receive(:mix_ingredients)
      expect_any_instance_of(described_class).to receive(:bake)

      described_class.new.make
    end
  end
end

However since mix_ingredients calls Foo.mix and bake calls Foo.bake would we also want to test that those get called in our test for make? The answer is it depends on whether these get called inside public or private methods.

Taking a look at mix_ingredients we see that it’s a public method. Since all public methods should be tested, we know that we will have a separate test for it and it will check if Foo.mix is called. Here is our updated test file with the test for mix_ingredients.

describe Cake do
  describe '#make' do
    it 'calls mix_ingredients and bake' do
      expect_any_instance_of(described_class).to receive(:mix_ingredients)
      expect_any_instance_of(described_class).to receive(:bake)

      described_class.new.make
    end
  end
  
  describe '#mix_ingredients' do
    it 'calls mix on Foo' do
      expect(Foo).to receive(:mix)

      described_class.new.mix_ingredients
    end
  end 
end

But what about bake? Since we don’t test private methods we won’t have a test for this. However, we still want to test that it calls Foo.bake. Since bake gets called when make gets called, we need to add a test for this in our make test.

describe Cake do
  describe '#make' do
    it 'calls mix_ingredients and bake' do
      expect_any_instance_of(described_class).to receive(:mix_ingredients)
      expect_any_instance_of(described_class).to receive(:bake)

      described_class.new.make
    end
  
    it 'calls bake on Foo' do
      expect(Foo).to receive(:bake)

      described_class.new.make
    end
  end
  

  describe '#mix_ingredients' do
    it 'calls mix on Foo' do
      expect(Foo).to receive(:mix)

      described_class.new.mix_ingredients
    end
  end
end

In summary, determining the scope of what to test inside of a method that calls other methods, depends on whether public or private methods get called within it. If a public method is called, then it will have it’s own test and you only need to test that this public method gets called. If a private method is called, then it won’t have it’s own test, so you’ll need to test what’s happening inside the private method.