Justin Meltzer
Justin Meltzer

Reputation: 13558

Why is my rspec test failing?

Here's the test:

        describe "admin attribute" do

        before(:each) do
          @user = User.create!(@attr)
        end

        it "should respond to admin" do
          @user.should respond_to(:admin)
        end

        it "should not be an admin by default" do
          @user.should_not be_admin
        end

        it "should be convertible to an admin" do
          @user.toggle!(:admin)
          @user.should be_admin
        end
      end

Here's the error:

  1) User password encryption admin attribute should respond to admin
 Failure/Error: @user = User.create!(@attr)
 ActiveRecord::RecordInvalid:
   Validation failed: Email has already been taken
 # ./spec/models/user_spec.rb:128

I'm thinking the error might be somewhere in my data populator code:

require 'faker'

namespace :db do
  desc "Fill database with sample data"
  task :populate => :environment do
    Rake::Task['db:reset'].invoke
    admin = User.create!(:name => "Example User",
                 :email => "[email protected]",
                 :password => "foobar",
                 :password_confirmation => "foobar")
    admin.toggle!(:admin)             
    99.times do |n|
      name  = Faker::Name.name
      email = "example-#{n+1}@railstutorial.org"
      password  = "password"
      User.create!(:name => name,
                   :email => email,
                   :password => password,
                   :password_confirmation => password)
    end
  end
end

Please let me know if I should reproduce any more of my code.

UPDATE: Here's where @attr is defined, at the top of the user_spec.rb file:

require 'spec_helper'

describe User do

  before(:each) do
      @attr = { 
        :name => "Example User", 
        :email => "[email protected]",
        :password => "foobar",
        :password_confirmation => "foobar" 
      }
    end

Upvotes: 4

Views: 2656

Answers (4)

jake
jake

Reputation: 2411

This does not seems to be ideal way of setting up test data. ie, using a rake task to populate the database.

A more standard unit testing and Rails practice would be to use both factory_girl or a test_fixture and transactional test fixture or database_cleaner gem.

Read a little bit about those, and they should be straight forward to use. They ensure, that each of your rspec test runs in isolation even when you run all of them together. That way, each test data for one test will not affect the other one.

Upvotes: 0

sorens
sorens

Reputation: 5060

before( :each ) is going to create a new user object from @attr. So if @attr isn't changing the values for its fields, and you have validations turned on to prevent duplicate, then on your 2nd test, the user object you created in the first test will collide with the one you are trying to create in the 2nd test.

There are other ways to go about testing your model without the database. For example, you can use test doubles to create and setup objects with exactly the data you want and then run your test to see if it behaves correctly. There is a [great book on RSpec, Cucumber and BDD] that could be a great source.

Edit: My apologies, I was confusing before(:each) with before(:all).

Upvotes: 0

zetetic
zetetic

Reputation: 47578

Try checking for existing users in the before block:

before(:each) do
  User.count.should == 0
  @user = User.create!(@attr)
end

If that fails, then another user exists with the same email. This could be because another before block created a user with the same attributes, or that the test database was not correctly cleaned out after a failure. For the latter case, try running rake db:test:prepare, and then run the spec again.

Upvotes: 2

Michelle Tilley
Michelle Tilley

Reputation: 159135

Check to be sure that there isn't a block further up your user_spec.rb that is calling User.create in a before(:each) block with the same email address. If your blocks are nested incorrectly, you'll get this error. For example, in the Rails tutorial, it's easy to accidentally nest your describe "admin attribute" inside your describe "password encryption" block, which uses the same before(:each) code.

Upvotes: 6

Related Questions