LearningRoR
LearningRoR

Reputation: 27242

Why does my test fail when I use FactoryGirl in this manner?

I'm testing my User model and studying how FactoryGirl works. When I do this in my user_spec.rb:

before(:each) do
  @user = User.new(username: 'ExampleUser', email: '[email protected]', timezone: 'Eastern Time (US & Canada)', password: 'example')
end

Everything passes, but if I do:

before(:each) do
  @user = FactoryGirl.create(:user)
end

It fails the test to see if the user's username and email are taken already.

1) User when username is already taken
     Failure/Error: it { should_not be_valid }
       expected valid? to return false, got true
     # ./spec/models/user_spec.rb:151:in `block (3 levels) in <top (required)>'

  2) User when email address is already taken
     Failure/Error: it { should_not be_valid }
       expected valid? to return false, got true
     # ./spec/models/user_spec.rb:142:in `block (3 levels) in <top (required)>'

Finished in 1.8 seconds
29 examples, 2 failures

These are the test:

  describe 'when email address is already taken' do
    before do
      user_with_same_email = @user.dup
      user_with_same_email.email = @user.email.upcase
      user_with_same_email.save
    end
    it { should_not be_valid }
  end

  describe 'when username is already taken' do
    before do
      user_with_same_username = @user.dup
      user_with_same_username.username = @user.username.upcase
      user_with_same_username.save
    end
    it { should_not be_valid }
  end

Can someone explain? I thought FactoryGirl was suppose to let me use it like User.new, my first example which works.

Upvotes: 2

Views: 462

Answers (2)

Baylor Rae&#39;
Baylor Rae&#39;

Reputation: 4010

Generally when testing a field, using Factory Girl, that has validates_uniqueness_of it's best to use a sequence.

When using a sequence, every time you create a record with FactoryGirl.create(:user), the username will always be unique. This allows you to work with "real" records in your database without having to manually correct for conflicting values.

factory :user do
  sequence :username do |n}
    "user_#{n}"
  end
end

Note: I don't like the idea of testing records that haven't been added to the database. I can't think of any solid reason why it would be a problem. The only problem I can think of is the fact that you won't be able to test associations.


Something else that I keep noticing with your questions is you use a before block and create instance variables. In RSpec there is a method called let will create the variable when it is needed.

This would make your user_spec.rb file work like so.

describe User do
  let(:user) { create(:user, :first_name => "John", :last_name => "Doe") }

  it "should get full name" do
    user.full_name.should == "John Doe"
  end
end

let also has a bang method that will create the variable whether it is used in the it block or not.

Upvotes: 1

Chris Salzberg
Chris Salzberg

Reputation: 27374

FactoryGirl.create actually creates the record, whereas User.new only instantiates the model but does not actually save the record.

If you want to only instantiate the model, you should use FactoryGirl.build:

before(:each) do
  @user = FactoryGirl.build(:user)
end

See the documentation for details.

So what I think is happening with your current code is that when you create the user with FactoryGirl.create, it actually saves the record with no validation issues (since the duplicate has not been created yet). When you save the user with the same email with user_with_same_email.save, it does not actually save that user but you don't see that. Then when you check if the original user is valid, it says yes because you already saved it before trying (and failing) to create the duplicate.

Make sense? Anyway just switch to FactoryGirl.build and both tests should pass.

Upvotes: 1

Related Questions