Categories
Agile Java Ruby

An issue with mock-driven development in dynamically-typed languages

First, let me make it clear that I really like BDD, I really like mocks, and I really like dynamic languages. RSpec does a pretty good job of combining all three.

However, there’s one disadvantage that duck-typed languages suffer when it comes to using mocks to drive the design of the interfaces between objects.

The following example is lifted from the excellent paper Mock Roles, not Objects, by Steve Freeman, Nat Pryce, Tim Mackinnon and Joe Walnes. If you haven’t already read it, it’s well worth it.

Here’s a test case:

public class TimedCacheTest {
  static final Object KEY = 1;
  static final Object KEY2 = 1;
  static final Object VALUE = "one";
  static final Object VALUE2 = "two";

  public void testLoadsObjectThatIsNotCached() {
    ObjectLoader mockLoader = mock(ObjectLoader.class);
    TimedCache cache = new TimedCache(mockLoader);

    mockLoader.expect(once()).method("load").with( eq(KEY) )
        .will(returnValue(VALUE));
    mockLoader.expect(once()).method("load").with( eq(KEY2) )
        .will(returnValue(VALUE2));

    assertSame( "should be first object", VALUE, cache.lookup(KEY) );
    assertSame( "should be second object", VALUE2, cache.lookup(KEY2) );

    mockLoader.verify();
  }
}

And here’s some code to pass the test:

public class TimedCache {
  private ObjectLoader loader;

  public TimedCache(ObjectLoader loader) {
    this.loader = loader;
  }

  public Object lookup(Object key) {
    return loader.load(key);
  }
}

In order to get this to compile1, you also need to introduce an interface:

public interface ObjectLoader {
  Object load(Object theKey);
}

When you’ve finished specifying the behaviour of TimedCache, you simply move on to an implementation of ObjectLoader, and repeat the process.

Now here’s something similar in Ruby. First the test:

KEY = 1
KEY2 = 2
VALUE = "one"
VALUE2 = "two"

describe "An object that is not cached" do
  before :each do
    @mock_loader = mock "loader"
    @cache = TimedCache.new @mock_loader
  end

  it "should be loaded" do
    @mock_loader.should_receive(:load).with(KEY).and_return VALUE
    @mock_loader.should_receive(:load).with(KEY2).and_return VALUE2
    @cache.lookup(KEY).should == VALUE
    @cache.lookup(KEY2).should == VALUE2
  end
end

And the code to make it pass:

class TimedCache
  def initialize loader
    @loader = loader
  end
  
  def lookup key
    @loader.load key
  end
end

The difference here is that Ruby doesn’t have interfaces. The mock object loader is just an object of no particular class, with a bunch of methods on it (OK, just the one in this case), which you then have to remember to include in the real implementation.

I wonder whether it would be useful to be able to print out a list of the methods that you’ve mocked, to give you a starting point when implementing the classes you’ve mocked. Here’s a very quick-and-dirty hack to Spec::Mocks::Proxy that prints a list to the console while the test’s running:


module Spec
  module Mocks
    class Proxy
        ...
      def verify_expectations
        # HACK STARTS HERE!
        puts "  mocked on #{@name}:"
        @expectations.each do |expectation|
          puts "    #{expectation.sym.to_s}(#{expectation.expected_args.join ', '})"
        end
        #HACK ENDS HERE
        @expectations.each do |expectation|
          expectation.verify_messages_received
        end
      end
      ...

Here’s the output:

An object that is not cached
  mocked on loader:
    load(1)
    load(2)
- should be loaded

Really this probably belongs in a custom formatter, but it doesn’t look like the right hooks currently exist to implement it that way.

[tags]bdd, mocks, ruby, rspec[/tags]


1You’ll also have to fix any typos I’ve introduced – I haven’t actually run any of this code.

Leave a Reply