[rspec-devel] mock_model with inheritance (patch)

Brian briandg at gmail.com
Fri Jun 8 20:08:59 EDT 2007


I'm new to mocking and stubbing, and I'm still learning about good
test/spec design, so my problem and solution might seem naive for any
number of reasons. My solution was to modify mock_model so that mocked
ActiveRecord objects could pretend to be part of inheritance
hierarchies.

Here's what the problem was. I have two ActiveRecord models associated
through a join model. Let's say they are Party and ContactMethod, and
ContactMethod has subclasses like PostalAddress, EmailAddress,
PhoneNumber, etc. Let's say I've defined some convenience methods to
access a Party's specific kinds of contact_methods....

  class Party < ActiveRecord::Base
    # ...
    has_many :contact_methods, :through => :join_model

    def postal_addresses
      contact_methods.select { |cm| cm.is_a?(PostalAddress) }
    end
    # etc ...
  end

(There may be a problem with my OO design here, but whatever.)
Originally I wrote my specs using all real ActiveRecord objects, which
worked okay until I had > 350 examples in spec/models that took > 20
seconds to run. I knew I'd eventually have to figure out what mocks
were all about before I started working on controllers, and the speed
issue finally galvanized me. I started with something like this:

  describe "Party with contact methods" do
    before do
      @jane = Party.create :first_name => "Jane", :last_name => "Doe"
      @postal = mock_model(PostalAddress)
      @jane.contact_methods << @postal
    end
  end

Right away that raises ActiveRecord::AssociationTypeMismatch, because
the collection expects ContactMethod objects, and the mock object
doesn't know that a PostalAddress is also a kind of ContactMethod. Nor
will this work:

  describe "Party with contact methods" do
    before do
      @jane = Party.create :first_name => "Jane", :last_name => "Doe"
      @postal = mock_model(ContactMethod)
      @jane.contact_methods << @postal
    end

    it "should be able to return specific kinds of contact methods" do
      @jane.should have(1).postal_addresses
    end
  end

The example fails, obviously, because @postal thinks it's just a
ContactMethod. Hmm, how can a mocked model say "Yes, I am a
ContactMethod" and also "Yes, I am a PostalAddress"? I suppose I could
do

  it "should be able to return specific kinds of contact methods" do
    @postal.stub!(:is_a?).and_return(true)
    @jane.should have(1).postal_addresses
  end

but that seems like cheating, or something, and it also seems too
drastic. The mock at that point would say yes to anything--"Yes, I am
a Hamburger, a Frankfurter, and a Donut."

I looked up the definition of mock_model and saw....

  def mock_model(model_class, stubs = {})
    # ...
    m.send(:__mock_proxy).instance_eval <<-CODE
      def @target.is_a?(other)
        other == #{model_class}
      end
    # ...
  end

That definition of #is_a? seemed more like #instance_of?. I tried this instead:

  def mock_model(model_class, *args)
    stubs = args.last.is_a?(Hash) ? args.pop : {}
    class_hierarchy = [model_class] + args
    # ... as before ...
    m.send(:__mock_proxy).instance_eval <<-CODE
      def @target.is_a?(other)
        #{class_hierarchy.inspect}.include?(other)
      end
    # ...
  end

And then I wrote my spec like this:

  describe "Party with contact methods" do
    before do
      @jane = Party.create :first_name => "Jane", :last_name => "Doe"
      @postal = mock_model(PostalAddress, ContactMethod)
      @jane.contact_methods << @postal
    end

    it "should be able to return specific kinds of contact methods" do
      @jane.should have(1).postal_addresses
    end
  end

And it worked.

So if this seems good, I've attached a patch. If it seems horribly
wrong, can someone tell me why?

Thanks,
Brian
-------------- next part --------------
A non-text attachment was scrubbed...
Name: mock_model_with_inheritance.patch
Type: application/octet-stream
Size: 1645 bytes
Desc: not available
Url : http://rubyforge.org/pipermail/rspec-devel/attachments/20070608/b0d13379/attachment-0001.obj 


More information about the rspec-devel mailing list