General

Gems

Web Based

Surrogate

Handrolled Mocking Framework

When testing, we often want to mock out some object. Perhaps it is expensive to load that code, or not yet written, or produces unreliable results, or interacts with external systems. Whatever the reason, we deal with this by using a mock object: an object that looks like the real one, but that allows us to inject return values, and query it after the fact to see how it was used.

There are many solutions for this already, but they primarliy are done with dynamic mocking, such as rspec-mocks. My issue with this approach is that we must define the mock object everywhere we wish to use it, allowing mistakes across definitions, and diffusing the interface across all the tests; dynamic mocking does not (in any of the cases I know of) enforce arity; and does not ensure that the methods we define match up with the methods on the real object, thus the mock can (and does) diverge from the real object, without tests failing.

Surrogate deals with this by giving you a way to declare what the interface is, in a real object, which will then generally behave as expected, but also giving you a standard way to override return values, query about what happened after the fact, and ensure congruity between the mock and real object. In fact, it does this in such a way that you can assert congruity between the mock class and any other class, allowing the Surrogate to be used as an interface that real objects can implement, and test their adherence to.

require 'surrogate/rspec'

class MockClient
  Surrogate.endow self
  define(:request) { 'default value' }
end

describe 'using the mock' do
  let(:client) { MockClient.new }

  it 'has defaults' do
    client.request.should == 'default value'
  end

  it 'can be overridden' do
    client.will_have_request 'injected value'
    client.request.should == 'injected value'
  end

  it 'can be asserted against' do
    client.was_not told_to :request
    client.request
    client.was told_to :request
  end
end

since we use the MockClient in our tests, we need to make sure that it looks like the real client that we will use in production.

require 'surrogate/rspec'

class MockClient
  Surrogate.endow self
  define(:request) { 'default value' }
end

class RealClient
  def request
  end
end

describe MockClient do
  it 'substitutes for the mock client' do
    RealClient.should substitute_for MockClient
  end
end

This would fail if the names differed, or if the arguments differed, or if there were methods that did not match across the two (there are options you can pass to substitute_for that will tell it what to care about)

Surrogate has a lot more functionality than this, it supports class-level methods, checks argument types, argument names, has support for predicates and verbs, factories for initializing with defaults (allowing for easy switching between rspec-mocks and Surrogate), it supports all of rspec-mocks argument matchers.

Surrogate is for handrolling mock objects.