[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