Using Mocha for ActiveRecord Partial Mocks with Finders

Mocha is the mocking library used by the Rails team, so it has understandably gained some traction among Rails developers. I have started using it over flexmock lately, but ran into some problems with partial mocks on ActiveRecord objects. The problem stemmed from the fact that ActiveRecord instantiates new records when returning records from finders, which meant that creating partial mocks for a particular record was difficult.

I created a helper method to make this easier, and so far it has cleaned up a bit of my tests.

Using #any_instance is not helpful in the cases I ran into, since I wanted to target a specific record for different expectations.

You could mock out the individual #find or #find_by_* methods as needed in each test, but that didn't seem ideal to me. Instead, I dug a bit into ActiveRecord to find what method was called when it actually instantiated the records after a find. I discovered that there is an #instantiate method which takes a hash of column=>value pairs from the finder to instantiate those records.

The following is the helper method which I created to do this:

 
def mock_active_records(*records)
    records.each do |record|
      record.class.stubs(:instantiate).with(
        has_entry('id', record.id.to_s)
      ).returns(record)
      record.stubs(:reload).returns(record)
    end
  end
 

Here is an example test:

 
require 'test_helper'
class UserTest < ActiveSupport::TestCase
  setup do
    @user = User.create!
    @non_pending_order = user.orders.create!
    @pending_order = user.orders.create! :pending => true
    mock_active_records @non_pending_order, @pending_order
  end
 
  test 'cancels pending orders when deactivating user' do
    @non_pending_order.expects(:cancel).never
    @pending_order.expects(:cancel).once
 
    @user.deactivate!
  end
end
 

This test makes sure that when I deactivate a user, that I call the cancel method on only the pending order.

Now, you may be asking "why don't I just test the side effects of cancel on the orders?" Well, I feel that what happens during cancel for an order is outside the scope of a unit test for user. In this example, cancel can have a multitude of side effects. In the context of a unit test for a user, I don't care what happens when I cancel an order. I only care that I do cancel it.
Also, if cancel needed to make an outbound call to another system, I wouldn't want an external system to be a dependency of my unit test. Mocking out the method removes any external and implementation dependencies in my unit tests (a functional test is a different matter, mind you).

Related Services: Ruby on Rails Development, Custom Software Development

Related posts:

  1. activerecord tests: modify activerecord logging without tripping rollback convenience
  2. Using ActiveRecord and Metaprogramming to Define Constants for Enumerated Types
  3. How to use will_paginate with non-ActiveRecord collection/array
  4. acts_without_database: Leverage ActiveRecord with Non-Database Backed Objects
  5. ActiveRecord create_or_update based on natural-key

Comments: 3 so far

  1. [...] Rails Development: Using Mocha for ActiveRecord Partial Mocks with Finders | Pathfinder Software De… [...]

    Pingback by Ennuyer.net » Blog Archive » Rails Reading - August 30, 2009, Sunday, August 30, 2009 @ 2:59 pm

  2. That works perfectly, thanks! Using it to fake specific records being returned created with FactoryGirl. (Oh, I love WP Hashcash myself.)

    Comment by Jason Boxman, Sunday, November 29, 2009 @ 8:34 pm

  3. Hi,

    It is obligatory to save the object before mocking into active record?

    Database insertions take much more time than queries.
    Probably i misunderstand something?

    Comment by dombesz, Thursday, February 4, 2010 @ 8:08 am

Leave a comment

Powered by WP Hashcash

Launch: Pathfinder Newsletter

    Get a monthly update on best practices for delivering successful software.

    Subscribe via email


    Subscribe via RSS      RSS icon

Topics

Search

WordPress

Comments about this site: info@pathf.com