Stabby Stubbing

Service objects are those concerned with a specific use-case in a system. A common example I’ve written time and again is Registration. Registration handles things like creating a User and its related objects, sending welcome emails, kicking off background jobs, etc. My applications are filled with them, and I love them all. They take complexity and isolate it so that it can be ignored when thinking about other objects, considered a “black box” when it’s working, and stubbed out in tests.

One way to make these even easier to stub out is to make their main entry point a method named call. Why this name? It’s the name of the method used to invoke a Proc in Ruby. This means that a service object can be stubbed in collaborators’ tests with a simple proc, lambda, or -> {} call. It’s especially nice combined with dependency injection:

class Collaborator
  def initialize(service, other_args)
    @service = service
    # ...
  end

  def work
    # I can do that.
    @service.call(self)
  end
end

Collaborator.new(-> (x) {}, "other").work

As added bonuses, you can send the .call message with the shortcut .(), which is syntax sugar added to Ruby for this method specifically. It takes arguments: service.(arg1, arg2).

On your service object, you can also define call on the class and delegate to a new instance to make the service object even easier to use:

class ServiceObject
  def self.call(x, y, z)
    new(x, y, z).()
  end

  def initialize(x, y, z)
    @x, @y, @z = x, y, z
  end

  def call
    # Work, work.
  end
end

ServiceObject.(1, 2, 3)