Problem

You want to write a unit test for this AccountController class. In order to test in isolation, you need to stub all the calls AccountController will make to models.

Because of the way ActiveRecord works, it’s tricky to write message expectations or stubs for an Account instance. You end up stubbing find and other AR methods, such as in the following example:

account = mock_model(Account, :id => "37")
Account.stub(:find).with("37") { account }
account.should_receive(:close)
post :close, :account_id => "37"

The problem is that approach leads to fragile tests due to the nature of ActiveRecord Query API. There is almost an “infinite” way of retrieving models. Whenever you change the way you retrieve the model, you’ll need to change your stubbing. For more information, I strongly recommend Avdi Grimm book’s Object on Rails.

IdentityMap to the rescue!

before(:all) { ActiveRecord::IdentityMap.enabled = true }
after(:all) { ActiveRecord::IdentityMap.enabled = false }

account = Factory(:account)
account.should_receive(:close)
post :close, :account_id => account.id

ActiveRecord::IdentityMap ensures that each object gets loaded only once by keeping every loaded object in a map.

When the AccountController retrieves an account, we can be sure it will be exactly the same object as in our test.

One drawback is a slower test. We are now creating an account in the DB only to benefit from Identity Map. It’s a trade off, we hope that more robust tests will save some time in the long run.

An alternative is to use any_instance stubbing, such as:

Account.any_instance.stub :close

But then, we loose the message expectation part and need to test something else. This approach could have side effects. As the name says, any instance of Account is stubbed!

What do you think?

Happy specing!


  • tech-angels posted this
  • -->
    blog comments powered by Disqus