[rspec-users] Best practice thoughts: Model helpers, mocks

James Hillyerd james at hillyerd.com
Wed Apr 18 20:07:57 EDT 2007


Hello!  I'm pretty new to unit testing, and have only been using RSpec
for a few weeks.   I found that for my controller specs, my setup
methods were getting very long building mocks/stubs for all the model
objects I needed to work with.

I've started creating helpers like the following for each of my
models.  The "min_" methods are short for "minimum", and are there to
give me the minimum set of data that will pass validation for that
model.  The "obj.send" code is to reduce the length of the data output
when RSpec compares expectations to results... A lot of my mocks were
several screenfulls long which sucks.

I've also started adding code that takes the attribs I define for the
mock_ method and stubs out :setter= methods too.

Am I going down the right path here?  (If so, I have a text mate
snippet that creates this stuff pretty quickly I can share)

module RenewalSpecHelper
  def min_renewal_attribs
    {
      :member_id => 1,
      :order_id => 1,
      :date_renewed => Date.new(2007, 1, 1),
      :membership_type_id => 1,
      :shirt_size => 'M',
      :amount_due => '5000'
    }
  end

  def min_renewal
    Renewal.new :attributes => min_renewal_attribs
  end

  def mock_renewal(stubs = {})
    obj = mock_model(Renewal, {
      :member_id => 1,
      :date_renewed => Date.new(2007, 1, 1),
      :membership_type_id => 1,
      :shirt_size => 'M',
      :billing_method => 'BM',
      :amount_due => '5000'
    }.merge!(stubs))

    obj.send(:__mock_handler).instance_eval "def inspect; @name; end"

    obj
  end
end


Below is an example of what I'd like to avoid, one of my controller
test setups.  It seems like each context, I end up pasting the
previous contexts setup, and then adding a few more lines, until it
ends up...

  setup do
    mem_types = Array.new
    @membership_type1 = mock_model(MembershipType)
    @membership_type1.stub!(:id).and_return(1)
    @membership_type1.stub!(:name).and_return('Name')
    @membership_type1.stub!(:price_cents).and_return(5000)
    mem_types << @membership_type1
    @membership_type2 = mock_model(MembershipType)
    @membership_type2.stub!(:id).and_return(2)
    @membership_type2.stub!(:name).and_return('Name')
    @membership_type2.stub!(:price_cents).and_return(10000)
    mem_types << @membership_type2
    @membership_type3 = mock_model(MembershipType)
    @membership_type3.stub!(:id).and_return(3)
    @membership_type3.stub!(:name).and_return('Name')
    @membership_type3.stub!(:price_cents).and_return(15000)
    mem_types << @membership_type3
    MembershipType.stub!(:find_available).and_return(mem_types)

    addl_members = Array.new
    [ 1, 2, 3, 3 ].each do |i|
      am = mock_model(AdditionalMember)
      am.stub!(:blank?).and_return(false)
      am.stub!(:membership_type_id).and_return(i)
      am.stub!(:email).and_return('foo at bar.com')
      am.stub!(:first_name).and_return('John')
      am.stub!(:last_name).and_return('Doh')
      am.stub!(:gender).and_return('M')
      am.stub!(:shirt_size).and_return('L')
      # am.stub!(:).and_return()
      addl_members << am
    end

    @signup_checkout = mock_model(SignupCheckout)
    @signup_checkout.stub!(:valid?).and_return(true)
    @signup_checkout.stub!(:shirt_size).and_return('L')
    @signup_checkout.stub!(:billing_method).and_return('PP')
    @signup_checkout.stub!(:additional_members).and_return(addl_members)
    session[:signup_checkout] = @signup_checkout

    @member = mock_model(Member)
    @member.stub!(:valid?).and_return(true)
    @member.stub!(:membership_type_id).and_return(1)
    @member.stub!(:membership_type).and_return(@membership_type1)
    @member.stub!(:full_name).and_return('Some Body')
    @member.stub!(:addr_line1).and_return('123 Main')
    @member.stub!(:addr_line2).and_return(nil)
    @member.stub!(:city).and_return('City')
    @member.stub!(:province).and_return('WA')
    @member.stub!(:postal_code).and_return('98008')
    @member.stub!(:visible_to_public).and_return(true)
    @member.stub!(:email_newsletter).and_return(true)
    # @member.stub!(:).and_return()
    @member.stub!(:lifetime_member=)
    @member.stub!(:membership_expires_on=)
    @member.stub!(:save!)
    session[:member] = @member
  end

(I have since learned that mock_models do stub out a unique ID value,
so I don't set those by hand on my newer tests)

Thanks for any opinions or insight. :)

-james

-- 
James A. Hillyerd <james at hillyerd.com>


More information about the rspec-users mailing list