Archives for March 2007

Fixture Groups

One of the things I like about working with Rails is test fixtures. However when the data model becomes more complex, the fixtures can become rather large. Worse yet, tests that require fixtures among two or more models feel clunky.

I wrote a plugin to help organize fixtures called fixture_groups.

./script/plugin install \

http://source.elevatorfight.com/public/fixture_groups

Example:

class BlahTest
  fixture_group :group1
  fixtures :foo, :bar

  ...
end

This will look in RAILS_ROOT/test/fixtures/group1 for the fixtures. If a fixture_group isn’t specified, then the fixtures will use the normal folder.

h3. Update 8/10/07

For the most part, I’ve given up on fixtures in my tests. Although I have it in my mind to check out fixture scenarios soon.

Duplicate Test Names

One of my biggest pet peeves of writing tests in ruby is based on something that makes ruby great. The openness of code.

...

def test_something
  # some assertions
end

...

def test_something
  # test with different assertions
end

...

Perhaps it’s my consistency towards test names or perhaps I’m focusing on the tree in the forest. But I’ve found myself in this problem more than a few times, editing the first test rather than the second, but the second essentially overrides the definition of the first.

In order to help detect this, I created a small rake task that searches through all tests, and checks the uniqueness of the test names within a test case.

It does ignore commented methods, however right now only accounts for # .

I may convert this into a plugin later, but until then save this in RAILS_ROOT/lib/tasks

> rake test:check_names
Everything looks good

> rake test:check_names
rake aborted 
Multiple methods ["def test_something"] in test/unit/some_test.rb

(See full trace by running task with --trace)

While this keeps an eye out for me, I’m still lazy enough not to run it all the time. I decided to make it a core task for continuous integration, and then check it manually when I’m stumped why tests are not behaving as they should.

Depth Merge

Merge is rather useful, but it has the limitation of staying shallow. Here is a simple extension of an implemented depth merge, with one caveat. Passing in true to delete_nils will remove the key if the value is nil.

Install as a Rails plugin:

./script/plugin install http://source.elevatorfight.com/public/merge_extensions

To gut out the meat just grab this file

An example of use:

# Without trimming nils
hash1 = { :a => "foo", :b => { :c => "bar"} }
hash2 = { :b => { :c => "blah"} }
hash1.depth_merge(hash2) #=> { :a => "foo", :b => { :c => "blah"} }

# With trimming nils
hash1 = { :a => "foo", :b => { :c => "bar"} }
hash2 = { :b => nil }
hash1.depth_merge(hash2, true) #=> { :a => "foo" }

Again a very simple utility, however it comes in handy with testing.

# In Model
class Foo < ActiveRecord::Base
  validates_presence_of :col1, :col2
end

# In Unit Test
...
def setup
  @default_attributes = { :col1 => "foo", :col2 => "bar" }
end

def test_missing_important_fields
  # A model with not only col1 == nil, but nil is never passed to the setter
  model = create_model(:col1 => nil)
  # do some checking on validity

  model = create_model(:col2 => nil)
  # again checking on validity
end

def test_something_else
  model = create_model
end

private
  def create_model(opts = {}) 
    SomeModel.new(@default_attributes.depth_merge(opts, true))
  end
...

Having a the depth part of the merge many not be handy in that scenario, but in functional tests they can help with composing the get/post

# In Functional Test
...
def setup
  ...
  @default_params = { 
    :id => 5, 
    :foo => {
      :col1 => "foo",
      :col2 => "bar"
    }
  }
end

def test_something
  post :some_action, params(:foo => { :col1 => nil })
  ...
end

def test_something_else
  post :some_action, params
  ...
end

private
  def params(opts = {})
    @default_params.depth_merge(opts, true)
  end
...

The premise for this is so tests become more concise, leaving only relevant information. Which in turn make the tests more readable.

Subdomain Assertions

Working with subdomains with rails can be easy, especially with [urlfordomain`](http://wiki.rubyonrails.org/rails/pages/Url+for+domain). However testing can be a pain.

assert_redirected_to helped when checking controller / action / parameters, but not asserting the subdomain. I rolled up a small assertion helper that can sit in test_helper.rb.

def assert_redirect_url(options = {})
  opts = {:only_path => false, :controller => @controller.controller_name}.merge(options)
  assert_equal url_for(opts), @response.redirect_url, "Error matching redirect url"
end

Example:

assert_redirect_url(:subdomain => "test-subdomain", 
  :controller => "test-controller", :action => "test-action")

Update 3/21/07

I recently found that assert_redirect_url collides with an existing rails test helper that is deprecated in Rails 1.2.2. By putting this function definition at the bottom of test_helper.rb it will be overridden. Of course you can always use an arbitrary name and avoid any conflicts.