Although we didn't have to use FactoryGirl to refactor common data required for models instances for tests, it reduced the amount of code we had to write.
We had a problem with both the rubygems repo's version of FactoryGirl 1.5.0 and 1.6.0 having some wierd UTF issues with the gemspec, so we got it from GitHub instead. Here's an example from Gemfile:
group :development, :test do
gem 'rspec-rails'
gem 'factory_girl_rails', '1.6.0', :git => "git://github.com/thoughtbot/factory_girl_rails.git"
end
We used bundle to install and then added FactoryGirl to spec/spec_helper.rb under the other requires:
require 'factory_girl'
We started by trying to use FactoryGirl's ability to load up each model's factory as its own file because that seemed cleaner, but because of some factories being dependent on other factories, we ended up defining all factories in spec/factories.rb. That way, the load order would be correct.After we created the factories, when we blew away our sqlite database with rake db:drop, rake db:migrate failed when attempting to run the migrations to create those tables because FactoryGirl choked not being able to find the tables associated with the models we referenced.
Even though there are other ways to get around this, to make it easier, we check for a table that indicates that the schema is setup. This is not optimal, but it works for now. Here's an example:
if ActiveRecord::Base.connection.table_exists? 'locations'
Factory.define :user do |u|
u.sequence(:netid) {|n| "test#{n}" }
end
Factory.define :location do |l|
l.sequence(:name) {|n| "test#{n}" }
end
Factory.define :widget do |b|
b.location (Factory.build(:location))
b.sequence(:number) {|n| "test#{n}" }
end
end
After running rspec at this point, we still saw all of the failing tests.Then we had to edit the tests. Here is a sample test so you can see the replacements we did. Basically we replaced the valid_attributes method with:
before(:each) do
@widget = Factory(:widget)
end
Then we replaced a lot of references to widget with @widget, with some exceptions. We had to change the way that we sent widget instances into posts, for example:
post :create, :widget => Factory.build(:widget).attributes.symbolize_keys
And did similar for :update. Here is an example rspec controller test with changes made:
require 'spec_helper'
describe WidgetsController do
before(:each) do
@widget = Factory(:widget)
end
describe "GET index" do
it "assigns all widgets as @widgets" do
get :index
assigns(:widgets).should eq([@widget])
end
end
describe "GET show" do
it "assigns the requested widget as @widget" do
get :show, :id => @widget.id
assigns(:widget).should eq(@widget)
end
end
describe "GET new" do
it "assigns a new widget as @widget" do
get :new
assigns(:widget).should be_a_new(Widget)
end
end
describe "GET edit" do
it "assigns the requested widget as @widget" do
get :edit, :id => @widget.id
assigns(:widget).should eq(@widget)
end
end
describe "POST create" do
describe "with valid params" do
it "creates a new Widget" do
expect {
post :create, :widget => Factory.build(:widget).attributes.symbolize_keys
}.to change(Widget, :count).by(1)
end
it "assigns a newly created widget as @widget" do
post :create, :widget => Factory.build(:widget).attributes.symbolize_keys
assigns(:widget).should be_a(Widget)
assigns(:widget).should be_persisted
end
it "redirects to the created widget" do
post :create, :widget => Factory.build(:widget).attributes.symbolize_keys
response.should redirect_to(Widget.last)
end
end
describe "with invalid params" do
it "assigns a newly created but unsaved widget as @widget" do
Widget.any_instance.stub(:save).and_return(false)
post :create, :widget => {}
assigns(:widget).should be_a_new(Widget)
end
it "re-renders the 'new' template" do
Widget.any_instance.stub(:save).and_return(false)
post :create, :widget => {}
response.should render_template("new")
end
end
end
describe "PUT update" do
describe "with valid params" do
it "updates the requested widget" do
Widget.any_instance.should_receive(:update_attributes).with({'these' => 'params'})
put :update, :id => @widget.id, :widget => {'these' => 'params'}
end
it "assigns the requested widget as @widget" do
put :update, :id => @widget.id, :widget => @widget.attributes.symbolize_keys
assigns(:widget).should eq(@widget)
end
it "redirects to the widget" do
put :update, :id => @widget.id, :widget => @widget.attributes.symbolize_keys
response.should redirect_to(@widget)
end
end
describe "with invalid params" do
it "assigns the widget as @widget" do
Widget.any_instance.stub(:save).and_return(false)
put :update, :id => @widget.id, :widget => {}
assigns(:widget).should eq(@widget)
end
it "re-renders the 'edit' template" do
Widget.any_instance.stub(:save).and_return(false)
put :update, :id => @widget.id, :widget => {}
response.should render_template("edit")
end
end
end
describe "DELETE destroy" do
it "destroys the requested widget" do
expect {
delete :destroy, :id => @widget.id
}.to change(Widget, :count).by(-1)
end
it "redirects to the widgets list" do
delete :destroy, :id => @widget.id
response.should redirect_to(widgets_url)
end
end
end