Listing 2. views/meetings/new.html.erb, Modified from the Default Scaffold to Allow the User to Enter One or More People
<h1>New meeting</h1>
<% form_for(@meeting) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :starting_at %><br />
<%= f.datetime_select :starting_at %>
</p>
<p>
<%= f.label :ending_at %><br />
<%= f.datetime_select :ending_at %>
</p>
<p>
<%= f.label :location %><br />
<%= f.text_area :location %>
</p>
<p>With:
<%= select( "person",
"person_id",
Person.all.collect { |p| [p.fullname, p.id] }, {},
{:multiple => true}) %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', meetings_path %>
more descriptive than the simple numbers would be.
You can even do one better than this, because fixtures understand the has_many :through associa-tions that I defined in the models. Just as in the Ruby code, I can add a person to a meeting with:
meeting.people << a_person
I can put the same sorts of information in the fixture file. For example:
one:
starting_at: 2009-05-10 00:48:12 ending_at: 2009-05-10 01:48:12 location: MyText
people: one, two two:
starting_at: 2009-05-10 00:48:12 ending_at: 2009-05-10 01:48:12 location: MyText
people: two
If you do things this way, you don’t want to define things in both the meeting_people fixture and in the meetings fixture. Otherwise, you might be in for some very strange errors. Note that fixture files are ERb (embedded Ruby) files, so you can have dynamically generated entries, such as:
one:
starting_at: <%= 5.minutes.ago %>
ending_at: <%= Time.now %>
location: MyText people: one, two
Now, how do you use these fixtures in your tests? It’s actually pretty straightforward. You need to load the fixtures you want with the fixtures method:
fixtures :meetings
By default, all fixtures are imported, thanks to:
fixtures :all
in test/test_helper.rb, which is imported automati-cally into all tests. Then, in your test, you can say something like this:
get :edit, :id => people(:one).id
This example (of a functional test) will load the person object identified as one in people.yml, invoking the edit method and passing it the ID of the appropriate fixture.
Factory Girl
For a small site, or when you can keep everything in your head, fixtures are just fine. I’ve certainly used them over the years, and I’ve found them to be an invaluable part of my testing strategy. But, factories are an alternative to fixtures that have become increasingly popular, both because they’re written in Ruby code, and they allow you to do all sorts of things that are difficult or impossible with YAML fixtures.
Factory Girl is one of the best known factories, written and distributed by the Thoughtbot compa-ny, and it is available as a Ruby gem. After installing Factory Girl on your system and bringing it into your application’s environment with:
config.gem "thoughtbot-factory_girl", :lib => "factory_girl",
:source => "http://gems.github.com"
in config/environment.rb, you will be able to use it.
Basically, Factory Girl allows you to create objects in Ruby, rather than load them from fixture files. No defaults are created for you by the generator, but that’s not a big deal, given how easy it is to use Factory Girl to create test objects.
Above, I showed how in a test environment using fixtures, you can grab the person object with a name of one by using the people method, and then passing a symbol:
get :edit, :id => people(:one).id
people(:one)is a full-fledged ActiveRecord object, with everything you might expect from such an object. Factory Girl works in a different way.
First, you need to create a test/factories.rb file, in which your factories are defined. (You also may create a test/factories/ directory, the contents of which will be Ruby files defining factories.)
To create a factory for people (that is, in place of people.yml), insert people.rb inside test/factories:
Factory.define :person do |p|
p.first_name 'Reuven' p.last_name 'Lerner' p.email '[email protected]' end
Now, inside the tests, you can say:
get :edit, :id => Factory.build(:person).id
or:
person = Factory.build(:person) get :edit, :id => person.id
w w w. l i n u x j o u r n a l . c o m august 2009 | 2 1
At first glance, this doesn’t seem too exciting.
After all, you could have done roughly the same thing with your fixture, right? But factories allow you to override the defaults:
person = Factory.build(:person, :first_name => 'Foobar') get :edit, :id => person.id
But wait, there’s more. You can set associations as follows:
Factory.define :person do |p|
p.first_name 'Reuven' p.last_name 'Lerner' p.email '[email protected]'
p.meetings {|meetings| meetings.association(:meeting)}
end
In other words, if you have created a meeting factory, you can incorporate it into your person factory, taking advantage of the association, using a fairly natural syntax.
An even more interesting idea is that of sequences. If your application needs to create a large number of test people, you might want each of those people to have a unique e-mail address.
(Never mind that the e-mail never will be sent.) You can do this with a sequence:
Factory.define :person do |p|
p.first_name 'Reuven' p.last_name 'Lerner'
p.sequence(:email) {|n| "person#{n}@example.com" } end
The first person created with this factory will have an e-mail address of [email protected];
the second will be [email protected] and so forth.
As you can see, Factory Girl is as easy to use as YAML fixtures, but it offers a great many capabilities that come in handy when testing Rails applications.
Factory Girl is a terrific library for factories, and it has become quite popular since it was first released.
But, not everyone liked its basic syntax, and one of those people was Pete Yandell, who decided that although the basic idea behind factories was sound, he wanted to use a different (and more compact) syntax for his factories. Thus was born Machinist, which uses a Sham object to describe fields in an object, which are then assembled into blueprints for specific objects. For example:
require 'faker'
# Define the fields that we will need Sham.first_name { Faker::Name.first_name }
Sham.last_name { Faker::Name.last_name } Sham.email { Faker::Internet.email }
# Now use these field definitions to create a blueprint Person.blueprint do
first_name last_name email end
Now you can use these blueprints to create test objects. For example:
person = Person.make()
As with Factory Girl, you also can override the defaults:
person = Person.make(:email => '[email protected]')
Conclusion
Fixtures have been a part of Rails testing practices since the beginning, and they still can be quite useful. But, if you’re finding yourself frustrated by YAML files, or if you want to experiment with some-thing that offers more flexibility and features, you might well want to try looking into factories. This month, I looked at two different libraries for creat-ing Rails factories, both of which are in popular use and might be a good fit for your project.I
Reuven M. Lerner, a longtime Web/database developer and consultant, is a PhD candidate in learning sciences at Northwestern University, studying on-line learning communities. He recently returned (with his wife and three children) to their home in Modi’in, Israel, after four years in the Chicago area.
AT THE FORGE
Resources
The home page for Ruby on Rails is
www.rubyonrails.com. Information about testing, including the use of fixtures, is in one of the excellent, community-written Rails guides at guides.rubyonrails.org/testing.html.
If you are interested in learning more about factories, a good starting point (as is often the case) is the Railscast site, with weekly screen-casts by Ryan Bates. The Railscast that talks about fixtures is at railscasts.com/episodes/
158-factories-not-fixtures.
Finally, the home page for Factory Girl is at dev.thoughtbot.com/factory_girl, and the home page for Machinist is at github.com/
notahat/machinist/tree/master.