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]