Rails convention calls for test data to be stored in fixtures. This works very well for the first few iterations, but this approach quickly becomes unwieldy. Often, many records are required, which can lead to poor record names like event_46. Even when naming conscientiously, it’s difficult to give each record a meaningful name in a global scope. When adding required fields to models, every record needs to be updated, which can be tedious for large test data sets, and very often the updates have an undesired impact on a broad set of tests.
We’d prefer to build up test data closer to home, in the test itself, so we can see the connection between the data and the expectations. Furthermore, if we structure our tests this way the data is isolated from other tests so we’re free to tinker with it without breaking those other tests. The drawback is that manual creation of ActiveRecord objects in your test code can become as tedious to write and maintain as fixtures.
Enter Generate – a technique that centralizes ActiveRecord model generation for test data, removes the need for fixtures, and avoids the tedium and verbosity of in-test model creation.
Example RSpec usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
describe Event, '#find_all_by_name' do it 'finds events with matching names' do event = Generate.event Event.find_all_by_name(event.name).should include(event) end it 'does not find events with different names' do event = Generate.event(:name => "some name") Event.find_all_by_name("a different name").should include(event) end it 'does not find private events' do event = Generate.event(:private => true) Event.find_all_by_name(event.name).should_not include(event) end end class Generate def self.event(options={}) Event.create!(options.reverse_merge(:name => "sample event name", :private => false)) end end |
The Generate class makes building an Event very convenient. The test only needs to specify attributes it cares about (if any) and Generate will worry about making the Event valid. If at some point the event requires a starts_at field, the Generate class can simply add that to the default attributes, and all tests will continue to pass without alteration. This becomes especially useful when building webs of objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
describe Event, '#attendee_count' do it 'returns the number of yes rsvps' do event = Generate.event(:attendees => [Generate.user, Generate.user]) event.attendee_count.should == 2 end end class Generate def self.event(options={}) attendees = options.delete(:attendees) || [] event = Event.create!(options.reverse_merge(:name => "sample event name", :private => false)) attendees.each do |attendee| Generate.rsvp(:event => event, :user => attendee) end event end def self.user(options={}) @user_count ||= 0 @user_count += 1 User.create!(options.reverse_merge(:email => "user#{user_count}@example.com")) end def self.rsvp(options={}) options[:user] ||= Generate.user options[:event] ||= Generate.event Rsvp.create!(options.reverse_merge(:response => "yes")) end end |
Once again, the test is very clear. A reader can tell exactly what’s going on without hunting through fixtures. Furthermore, adding attendees to the Event in this test will not affect any other tests.
We’re using this approach on a large project currently under development. As our project has grown, the complexity of our data model has increased, making our ActiveRecord models more and more interrelated. But rather than feeling the pain of fixture maintenance and test adjustment, we’re able to move freely and quickly by updating one or two Generate functions at a time without impacting any other tests, while maintaining the flexibility to test anything new that comes our way.
The nice thing about Generate is that it doesn’t require a supporting library or up-front investment in tool building, short of knowing how to use ActiveRecord to instantiate your models. The trick is in the technique itself – leaving you the option to apply it in your projects in whatever way best meets your needs.
References:
The Generate technique is very similar to the ObjectMother testing pattern. Please see “ObjectMother: Easing Test Object Creation in XP” by Peter Schuh and Stephanie Punke.


5 Comments
If you haven’t already, take a look at Fixture Replacement. It’s pretty snazzy and tries to accomplish the same goal.
I hadn’t seen FixtureReplacement before, but it does look nice and snazzy. It has a few things we don’t. In particular it can do new_event and get an unsaved event with valid data.
The only weakness I see in FixtureReplacement is that it doesn’t appear to allow creation of dependent objects in the same call. If I’m creating an event with several RSVPs, I need to build them separately.
That said, FixtureReplacement looks like a very slick tool, and could probably be extended to support dependent object creation. It may already, but my cursory review didn’t come across any such support.
Here is another example of the same model. http://www.dcmanges.com/blog/38
Dave, the comment notifications are set up and working.
I read your blog for a long time and should tell that your posts are always valuable to readers.