I recently read the POODR book. In it Sandi Metz talks about verifying incoming and outgoing messages of the public interface of the object being tested.

Its the Interface part that I want to focus on.

Consider this sequence diagram.

action

The methods invoked are the API of each object

We want to create a test for the Action object

describe Action do
  context 'unit test using a mock' do
    before do
      @service = double('service')
      @persistor = Object.new
    end

    subject { described_class.new(@service, persistor: @persistor) }

    it 'calls its collaborators' do
      expect(@service).to receive(:run).with(@persistor)
      subject.create
    end
  end
end

Here we verify the behaviour by specifying the method that should be called and the args

However there is a way to construct the spec to be immune to the API method names

Consider this spec

describe Action do
  context 'unit test using a substitute implementation of the interface' do
    before do
      @service = SpecHelpers::ActionService.new
    end

    subject { described_class.new(@service) }

    it 'calls its collaborators' do
      subject.create
      expect(@service.invoked_correctly?).to be_true
    end
  end
end

If we define a module that represents the interface e.g. Runnable.rb

module Runnable
  def run(*args, &block)
    internal_run(*args, &block)
  end

  private

  def internal_run(*args, &block)
    # override in class
    # this is for testing
    @invoked_correctly = true
  end
end

Then we include this in any class that can be injected into the Action initializer. Perhaps we have a SubscribeService and an UnsubscribeService, but we also include this in our SpecHelpers::ActionService class too. By overriding the method internal_run (you can call this method anything you want) in all included classes, they can be said to implement the interface.

module SpecHelpers
  class ActionService
    include Runnable
    def invoked_correctly?() !!@invoked_correctly; end
  end
end

and

class Service
  include Runnable

  private

  def internal_run(context)
    puts 'Service did something with context: ' + context.class.name
  end
end

The default implementation of internal_run in the module sets the ivar @invoked_correctly. You can be pretty specific about what invoked_correctly means, in terms of arguments passed and it can document how you expect the Interface to be used, directly in the interface and not scattered around in tests or implementations.

Or you could alse have a module just for testing that provides the invoked_correctly? method

module SpecHelpers::InvokedCorrectly
  def invoked_correctly?()
    !!@invoked_correctly
  end
end

then the SpecHelpers::ActionService would be

module SpecHelpers
  class ActionService
    include Runnable
    include InvokedCorrectly
  end
end

So if any aspect of the interface definition changed, as long as you you also changed the test helper implementations too (they are proper implementations after all) your spec should still pass.

Does this seem like too much ceremony? Some would say yes; I say no - but it works best if you always pass the dependencies into the class somehow.

so the the Service class might look like this

class Service
  include Runnable
  include Callbackable

  def initialize(resource, context,
      policy: CreateResourcePolicy.new(context, resource),
      persistor: ResourcePersistor.new(context, resource)
    )
    @resource, @context = resource, context
    @policy, @persistor = policy, persistor
  end

  private

  def internal_run
    return callback(:denied) unless @policy.permit?
    if @persistor.persist
      callback :created
    else
      callback :errored
    end
  end
end

Here Policy and Persistor specific classes also implement interface modules.

If a set of test implementations e.g FailPolicy, FailPersistor, RaisesPersistor are built, these can be pushed into specs at any point to simulate variations.



blog comments powered by Disqus

Published

18 April 2014

Category

ruby

Tags