Latest from the Bamboo Blog

Defining interfaces through mocking jonathan

25 Feb 2007

This post is a re-work of my Barcamp presentation I did the other day on good OO design though mocking.

Now when I first started doing TDD over six years ago the major thing that struck me was how well designed the interactions between associated objects were compared to applications I’d written in the past without TDD. This was all because by writing test first I was forcing myself to think about how my objects were going to interact with my tests and thus with other objects within the system. Another thing I discovered was also how using mocks even more so allowed me to concentrate even more on the interfaces of the object while developing my models. OK so thats a bit of a lie, back then when I was doing TDD I was doing J2EE development with EJB 2.0 and as we all know this led to anaemic models because all our business logic had to be pulled in via service objects. So no, back then I wasn’t thinking too much about the interfaces of my “models”, more on my service objects;).

I’m going to run through a fairly trivial example just to give you a little taste of what I’m talking about. I’m going to be using “RSpec”:rspec.rubyforge.org with its built in mocking support in my examples but I could of easily used Test::Unit with “Flexmock”:onestepback.org/software/flexmock/ or “Mocha/Stubba”:mocha.rubyforge.org/.

Step one - Make a cup of tea

Step two - Install the RSpec gem


$ sudo gem install rspec

Step three - Create a Rails application, in this case we’re going to work on a small part of an auction application.


$ rails rspec_auction
$ cd rspec_auction

Setup both your development and test database by editing the database.yml. We won’t actually be hitting the database in the tests but unfortunately you’ll need to set it up.

Step four - Install the RSpec on Rails plugin and generate the helpers and directories that the plugin needs.


$ ruby script/plugin install \ 
svn://rubyforge.org/var/svn/rspec \
/tags/REL_0_7_5_1/rspec_on_rails/vendor/plugins/rspec_on_rails

$ script/generate rspec

Step five - Make another cup of tea.

Step six - Lets create an Auction model. Now the RSpec plugin for Rails provides you with several generators which will be familiar to you if you’ve used the Rails generators before.


$ script/generate rspec_model auction

Step seven - Hopefully you’ve already got the project open in a text editor or your favourite IDE. We need to make a small alteration the the file spec_helper.rb otherwise we’re going to have problems with Activerecords type checking. Yes, you heard me correctly, I said type checking;). Anyways at the bottom of the spec_helper.rb file you’ll need to add the following lines.


module ActiveRecord
  module Associations
    class AssociationProxy
      def raise_on_type_mismatch(record)
        # do nothing
      end
    end
  end
end

Oh and you’ll also need a basic migration file for your auction model so in the file 001_create_auctions.rb that’s already been generated for you add the following:


class CreateAuctions < ActiveRecord::Migration
  def self.up
    create_table :auctions do |t|
      t.column :title, :string
      t.column :description, :text
      t.column :updated_at, :datetime
      t.column :created_at, :datetime
      t.column :expires_at, :datetime
      t.column :starting_bid, :float
      t.column :active, :boolean
    end
  end

  def self.down
    drop_table :auctions
  end
end

Step eight - OK, now we’re all set to do some coding. Open up the auction_spec.rb file and the first thing you’ll see is something called a context with a description and specifications. When testing our model we may have multiple contexts all depicting the model in different states such as a new auction, an auction about to expire or an auction that has been canceled for example.

In this case the default context generated for us is “Given a generated auction_spec.rb with fixtures loaded”… Not exactly what we’re looking for. So delete this entire context including the specification and replace it with this:


require File.dirname(__FILE__) + '/../spec_helper'

module AuctionSpecHelper
  #Valid attributes for an auction
  def self.valid_auction_attributes
    {
     :title => "Big bamboo",
     :description => "One caring owner, still in good condition",
     :expires_at => 7.days.from_now,
     :starting_bid => 0.20,
     :active => true
    }
  end
  
  def self.invalid_auction_attributes
    {
      :title => nil,
      :description => nil,
      :expires_at => 7.days.ago,
      :starting_bid => "moo",
      :active => true
    }
  end
  
end

context "A general auction" do
  
  setup do
    @auction = Auction.new
  end
  
  specify "should have errors given invalid values" do
    @auction.attributes = AuctionSpecHelper.invalid_auction_attributes
    @auction.valid?.should_equal false
  end
  
  specify "should have no errors given valid values" do
    @auction.attributes = AuctionSpecHelper.valid_auction_attributes
    @auction.valid?.should_equal true
  end
  
end

The module helper at the top of the source should be self explanatory. I’m using it to store hashes of invalid and valid attributes for an auction.

Run the model spec’s by typing the following at the command line:


rake spec:models

You’ve probably noticed that I’ve shot ahead and added not one but two specifications. There wouldn’t be much point in the “should have no errors” specification as I haven’t actually written any constraints in the auction model yet. Really my tests or specifications in this case should always fail first and then it’s up to me to do what ever is required to make them spec pass…. Even if involves secret underground Artic bases… Eerrmm OK, maybe not. Anyway, as you can tell I’ve added the first specification in at the top to intentionally fail. Let’s make this pumpkin pass. Make your auction.rb look like this:


class Auction < ActiveRecord::Base
  
  validates_presence_of :title
  validates_presence_of :description
  validates_numericality_of :starting_bid
  
end

If you run the specifications now you’ll see that they all now pass.

Step nine - I’m going to digress for a minute and talk about stubs and mocks. If you don’t already know what a stub or a mock is then “here”:www.martinfowler.com/bliki/TestDouble.html is a good description of the various terms. Now you’ve probably been told to use stubs and/or mocks when working with systems external to your application, 3rd party libraries or dependencies that aren’t solely related to what you’re currently testing. In this case the database is a dependency so lets stub out any database calls. The only problem is that we’re currently not hitting the database, so let write a specification that would normally hit the database, but errrm doesn’t. Sorry if I sound like a fortune cookie. Add this context and specifcation to the auction_spec.rb file.


context "An auction that we want to remove" do
  
  setup do
    @auction = Auction.new
    @auction.attributes = AuctionSpecHelper.valid_auction_attributes
    @auction.stub!(:save!).and_return(:true)
  end
    
  specify "should be able to deactivate auction" do
    @auction.deactivate!
    @auction.active?.should_equal false
  end
  
end

The first thing you’ll see is that I’ve actually stubbed out the default Activerecord save! method of the auction model so that it doesn’t hit the database. You currently don’t see me calling save! but thats a coming implementation detail which will become obvious in a few minutes. Of course if you try running the above you’ll get a few errors, the first being that there isn’t a deactivate! method on auction yet. Let’s fix that now, inside auction.rb add:


def deactivate!
    self.active = false
    #Aha! The fabled save! method!!!
    self.save!
 end

OK, the spec should now pass, but you know what? Call me picky but I’d like to hide the active attribute of the model, I don’t want any Tom, Dick or Tony Blair playing with the auction innards. Let’s make the following modifications:

In auction.rb add:


def activated?
    self.active?
end

And then change the previous spec to:


specify "should be able to deactivate auction" do
    @auction.deactivate!
    @auction.activated?.should_equal false
 end

The specifications should now still pass.

Step ten - Let’s now interact with another model, in this case it’s going to be a user model as our auction belongsto one owner who created the auction. Things are going to start getting interesting. Add the following context and specification to your auctionspec.rb file:


context "An auction with an owner" do
  
  setup do
    @auction = Auction.new
    @auction.attributes = AuctionSpecHelper.valid_auction_attributes
    @owner = mock('user')
    @owner.stub!(:new_record?).and_return(false)
    #make sure you put in the Activerecord type checking hack in 
    #the spec_helper.rb file or this bit will go BOOM BOOM
    @auction.owner = @owner
  end
  
  specify "should have an associated owner" do
    @auction.owner.should_not_equal nil
  end
  
end

OK, so this isn’t a very helpful test/spec in itself. However did you notice that we didn’t drop to the command line, run the RSpec model generator, create a User model migration, decide what fields to put into our migration, run the migration and then add various methods to our User class that our Auction model might need to interact with. Instead we can concentrate purely on our Auction model’s business logic and it’s interfaces/interactions with other models. You’ll find that by using stubs and mocks it allows you to experiment a little more easily without detracting from your train of thought.

I think here’s a good place to stop for now. Next time in part two I’ll introduce you lovely people to more advanced examples of mocking, but first some sleep. Good night chaps (and chapettes).

Tagged with “Mocks”:technorati.com/tag/mocks, “Ruby”:technorati.com/tag/ruby, “Mocking”:technorati.com/tag/mocking, “RSpec”:technorati.com/tag/rspec

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.