Use Hash#except to easily test Rails validations

Ruby on Rails provides a large API for web development, including adding new methods to Ruby's base classes. One method I don't see used that often is Hash#except. From the Rails API documentation:

Return a hash that includes everything but the given keys. This is useful for limiting a set of parameters to everything but a few known toggles:

@person.update_attributes(params[:person].except(:admin))

A great use I found for it is to help test validation methods on Model objects. By defining a method that will return all the valid attributes on a model, you can easily exclude the ones you want to test with Hash#except.

I'm using RSpec in this example but the same idea works with Test::Unit or any other testing framework. I created a method valid_attributes that will return the attributes my Model needs to be valid. In this case my SystemNotification object needs a body, subject, and some users.

module SystemNotificationSpecHelper
  def valid_attributes
    user1 = mock_model(User, :mail => 'user1@example.com')
    user2 = mock_model(User, :mail => 'user2@example.com')
    return { 
      :body => 'a body',
      :subject => 'a subject line',
      :users => [user1, user2]
    }
  end
end

I used mocks for the users because I don't care about them, only that they are present. So to test if my object is valid, I can easily just pass in valid_attributes to my new method:

describe SystemNotification, "valid?" do
  include SystemNotificationSpecHelper

  it 'should be valid with the body, subject, and users' do
    system_notification = SystemNotification.new(valid_attributes)
    system_notification.valid?.should be_true
  end
end

Now I want to write some more specs to make sure if a SystemNotification is invalid if it's missing any of the required attribute. Since valid_attributes is a Hash, I can use Hash#except to remove each attribute individually and make sure the object is invalid for each case.

describe SystemNotification, "valid?" do
  include SystemNotificationSpecHelper

  it 'should be valid with the body, subject, and users' do
    system_notification = SystemNotification.new(valid_attributes)
    system_notification.valid?.should be_true
  end
  
  it 'should be invalid without a subject' do
    system_notification = SystemNotification.new(valid_attributes.except(:subject))
    system_notification.valid?.should be_false
  end
  
  it 'should be invalid without a body' do
    system_notification = SystemNotification.new(valid_attributes.except(:body))
    system_notification.valid?.should be_false
  end
  
  it 'should be invalid without any users' do
    system_notification = SystemNotification.new(valid_attributes.except(:users))
    system_notification.valid?.should be_false
  end
end

This is just a simple example but you can use Hash#except in many other places:

  • updating attributes like the Rail documentation sample
  • copying values from one object to another
  • extracting options from a argument

If you'd like some extra practice, refactor my specs to make sure SystemNotification#errors is populated based on what attribute failed. You can find the full code on GitHub, just send me a pull request when you're done.

Eric

Tagged: rails rspec ruby testing