Just A Summary

Piers Cawley Practices Punditry

Doing the fixture thing 6

Posted by Piers Cawley Sun, 05 Aug 2007 07:39:00 GMT

Fixtures suck! Mocks rock! Don’t you dare let your tests touch the database!

Well… yes… I suppose. Except, mocking can be a complete pain in the arse too (made slightly less of a pain in the arse if you use the null object options) – it’s awfully easy to end up with huge long setup methods that spend all their time faking out a mock object and about two lines testing what you need.

I’m sure someone’ll be along to argue that this is evidence of lazy design on my part. Well, “Yah boo sucks!” to the lot of ’em. Fixtures can be exceedingly useful.

Spooky action at a distance

The biggest problem I have with Rails’ current implementation of fixtures is… oh, where to start… it’s probably the action at a distance aspect of them. Your test is here, but your fixture is defined some way over there, in a random collection of yaml files.

Then there’s the problem of remembering which fixtures you need to have loaded for a particular test, and it all starts getting horrible very quickly.

One way of addressing this is to use fixture scenarios and fixture scenario builder, which works around the problem of remembering what to load and means you don’t have to write your fixtures in YAML. What it doesn’t work around is the action at a distance issue (but I’m betting it wouldn’t be too hard to repurpose the fixture scenario builder to let you declare the fixtures you need for a particular test/spec in the same place as you do the testing).

At work though, we came up with another way of making it easy to build your fixtures right in the test file. Our current approach is still a little bit clunky, and it’s nowhere near the point where I can turn it into a library, but I think it’s worth discussing anyway.

Exemplars

The question to ask is, what do you use a fixture for? Most of the time, what a test needs is a mostly generic instance of your class which will pass the validations, and which has maybe a couple of attributes set to particular values.

Let’s say you have a user class. As is common with such things, your user has a username, an email address and a password. As is so often the case, the usernames and email addresses must be unique, and the password must not be blank. Let’s say you’re working on a tagging system (isn’t everyone). Here’s the sort of specification you might write:

context "A taggable object" do
  setup do
    # makes a taggable object and a couple of users
  end

  it "Should aggregate taggings" do
    @first_user.tag(@taggable, "tag")
    @second_user.tag(@taggable, "tag")

    @taggable.save!; @taggable.reload

    @taggable.should have(1).tags
  end
end

In this context, the only thing you need from those user objects is that they behave like @users, are distinct and valid. You could simply set up a couple of users your users.yml fixture, but the approach we took at work went something like this:

class User
  class << self
    @@exemplar_count = 0
    def exemplar(overrides = {})
      @@exemplar_count += 1
      with_options(:username => "user#{@@exemplar_count}", 
                    :email => "user#{@@exemplar_count}", 
                    :password => "fredisabadpassword") do |maker|
        maker.new(overrides)
      end
    end

    def create_exemplar(overrides = {})
      returning(exemplar(overrides)) {|user| user.save}
    end

    def create_exemplar!(overrides = {})
      returning(exemplar(overrides)) {|user| user.save!}
    end
  end
end

This lets us write setup code like:

setup do
  @first_user = User.create_exemplar!
  @second_user = User.create_exemplar!
  @taggable = ...
end

In tests where you need an exemplar with a specific property, you can write Model.exemplar(:tested_attribute => specific_value) – the way the overrides work means you only have to describe the ‘interesting’ bits and the obscuring dust involved in simply building a valid object is swept under the carpet.

Homework

  • If you’re familiar with the Object Mother pattern, this might seem a little familiar, with the wrinkle that, instead of having a factory class, we just push the exemplar builder directly onto the model class.
  • If you start implementing exemplars yourself, you’ll probably spot a good deal of repetitive coding. I’ve not extracted a library yet because I’ve not quite come up with an interface that I like. Can you come up with a good way of doing it? Can you implement it?
  • What did I miss?
Comments

Leave a response

  1. Avatar
    Chris about 14 hours later:

    Dan Manges wrote about a scarily similar solution he called Fixin Fixtures with Factory — similar even down to the naming style.

    This was actually my first approach doing fixtures in Ruby. I had a fixtures.rb file which I gave create_ methods that had inner classes which would be mixed into their corresponding AR classes at test time. The problem was giving up all the nice stuff Rails’ tests give us — transactional fixtures, clearing the database between tests, and whatnot. Dan’s solution doesn’t address these issues, I’m curious how you guys get around them?

    (BTW, love the new blog style and the live comment preview!)

  2. Avatar
    Piers Cawley about 16 hours later:

    Unless I’m going mad, I just turn on use_transactional_fixtures and, as if by magic, I get my tests wrapped in a transaction/rollback regardless of how I build my fixture.

    Or am I going mad?

    And if that doesn’t work, it’s the work of not very long at all to create a bunch of completely empty .yaml files and choose use_preloaded_fixtures to load up a completely empty database.

    And thanks; I wish I could take credit for writing the live preview, but I just lifted it from http://livepipe.net/projects/control_textarea/textile and I’m loving it to death.

    When I finally get back to typo hacking after the current work rush, I intend to add both it, and its sibling that does live markdown preview to the default Typo style (and to the typo back end previewer – the old ‘round trip to the server’ preview is rather heavy by comparison).

  3. Avatar
    Dan Manges 2 days later:

    Yeah, use_transactional_fixtures does a database rollback at the end of each test even without using fixtures. I updated my post to mention that.

  4. Avatar
    Pat Maddox 3 days later:

    Hey Piers,

    Really liked this idea. Here’s my plugin:
    http://evang.eli.st/blog/2007/8/8/doin-the-fixtures-plugin-thing

  5. Avatar
    Scott Taylor 5 months later:

    Yep – I ’m with Pat. – I draw much inspiration from this post:

    FixtureReplacement (http://replacefixtures.rubyforge.org)

  6. Avatar
    Joe W. about 1 year later:

    This is one of the reasons unit-testing in a prototype-based language like JavaScript is so nice. The concept of an Examplar, is basically an object prototype, so creating mocks is trivial.

Comments



Just A Summary