Rspec: Validation failed: Name has already been taken

24k Views Asked by At

When running my specs, I am stopped by a FactoryGirl error before rspec can even iterate through them.

Finished in 0.18709 seconds (files took 1.57 seconds to load)
0 examples, 0 failures

/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/factory_girl-4.5.0/lib/factory_girl/linter.rb:14:in `lint!': The following factories are invalid: (FactoryGirl::InvalidFactoryError)

* program - Validation failed: Name has already been taken (ActiveRecord::RecordInvalid)
    from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/factory_girl-4.5.0/lib/factory_girl/linter.rb:4:in `lint!'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/factory_girl-4.5.0/lib/factory_girl.rb:59:in `lint'
from /spec/support/factory_girl.rb:9:in `block (2 levels) in <top (required)>'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/example.rb:333:in `instance_exec'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/example.rb:333:in `instance_exec'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/hooks.rb:357:in `run'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/configuration.rb:1559:in `block in run_hooks_with'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/configuration.rb:1559:in `each'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/configuration.rb:1559:in `run_hooks_with'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/configuration.rb:1525:in `with_suite_hooks'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/runner.rb:109:in `block in run_specs'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/reporter.rb:62:in `report'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/runner.rb:108:in `run_specs'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/runner.rb:86:in `run'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/runner.rb:70:in `run'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/lib/rspec/core/runner.rb:38:in `invoke'
from /.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.3/exe/rspec:4:in `<top (required)>'
from /.rbenv/versions/2.2.2/bin/rspec:23:in `load'
from /.rbenv/versions/2.2.2/bin/rspec:23:in `<main>'

Without the associated program in the campaign factory, my tests run fine with 15 examples, 2 failures.

Here is my Factory..

FactoryGirl.define do

    factory :campaign do |x|
        x.sequence(:name) { |y| "Q6 201#{y}" }
        x.sequence(:comment) { |y| "JIRA OI-6#{y}" }
        channels ["Folder", "Fax"]
        program
    end

    factory :program do
        name "Caspian Star"
    end

    factory :plan do
        name "Third Storm Connect"
    end
end

My relevant models..

Class Campaign < ActiveRecord::Base
    belongs_to :program
    validates :program, presence: true

    belongs_to :plan
end

Class Program < ActiveRecord::Base
    has_many :campaigns
end

It definitely has to do with setting up the associated Program with Campaign but I can't figure out how to do it right. My intention is to not create multiple instances of program but rather have the campaign associate with an existing one - or the one I create via FactoryGirl.

When using FactoryGirl's association :program method, I get the same error. It doesn't seem to matter what I name the program factory to either. I am also using DatabaseCleaner to clear the test database after it runs.

I'm currently trying to test the validates :program, presence true but keep running in circles with this.

Any help is highly appreciated.

UPDATE

Here are the specs as requested.

    describe "Relationships" do
        it { should belong_to :program }

        ...some unrelated relationship specs..
    end

    describe "Validations" do
        it { should validate_presence_of :name }
        it { should validate_uniqueness_of :name }
        it { should serialize :channels }
        it { should validate_presence_of :comment }
        it { should validate_presence_of :program }
    end


    it "serializes column into Array" do
      obj = build(:campaign)
      obj.channels = [1,2,3]
      obj.save!
      expect(obj.reload.channels).to eq [1, 2, 3]
    end

    it 'validates associated campaign' do
      campaign = build(:campaign)
      expect(campaign.save).to be_valid?
      expect(campaign.errors).to eq "You need to choose a Program."
    end
end

UPDATE #2

After experimenting with some of the answers below, I can confirm that sequence does not fix the error. However, when I completely remove the FactoryGirl association yet instantiate the association in the specs - I recieve the error Validation failed: Program can't be blank. Note, my specs are still not running..

Finished in 0.19413 seconds (files took 1.54 seconds to load)
0 examples, 0 failures

Something is going on before it hits my specs and I believe the answer is what the error refers to lint! I'm not too familiar with 'lint', as I set my Factory up following a blog. I checked the documentation and it appears that I have it set up correctly...however, it runs my Factories through my validations before the database is cleaned as well as before it runs any specs.

This is a problem when I want to validates :program, presence: true and instantiate it in the spec. 'FactoryGirl.lint' interrupts my test letting me know it can't be blank when that is exactly what I want the Factory to look like. So far, the only solution I can think of is to disable lint as I was able get my specs to run with it disabled.. but now that I understand it more, I can see how it could become very useful.

Is there a win-win situation for this scenario? Can I still have lint while preserving validations and associations the way I need them to be?

Here's a look at my spec/support/factory_girl.rb where lint exists..

 RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods

  config.before(:suite) do
    begin
      DatabaseCleaner[:active_record].strategy = :transaction
      DatabaseCleaner.clean_with(:truncation)
      DatabaseCleaner.start
      FactoryGirl.lint
    ensure
      DatabaseCleaner.clean
    end
  end
end
5

There are 5 best solutions below

1
On

Does Program validate uniqueness of it's name attribute? If so, you'll need to redefine the program factory to generate a unique name for each instance it generates.

factory :program do
  sequence(:name) { |n| "Caspian Star #{n}" }
end

Or if you have a particular program you've seeded (or otherwise guaranteed to exist) and a way to access it, you could use a block when declaring the association

factory :campaign do |x|
  ...
  program { Program.some_program }
end
5
On

You're probably instantiating two programs and a program's name in your factory is hard-coded. Make it a sequence (like you did for campaigns) and you will be good to go.

* Update * If you want your campaigns to have the same Program, you have to remove the program assignment from within the Campaign's factory and pass it explicitly in your specs. Like this:

FactoryGirl.define do

  factory :campaign do |x|
    x.sequence(:name) { |y| "Q6 201#{y}" }
    x.sequence(:comment) { |y| "JIRA OI-6#{y}" }
    channels ["Folder", "Fax"]
    # no more program here
  end

  factory :program do
    name "Caspian Star"
  end

  factory :plan do
    name "Third Storm Connect"
  end
end

And your specs:

let(:program) { FactoryGirl.create(:program) }
let(:campaign) { FactoryGirl.build(:campaign, program: program) }

describe "Relationships" do
    it { campaign.should belong_to :program }

    ...some unrelated relationship specs..
end

describe "Validations" do
    it { campaign.should validate_presence_of :name }
    it { campaign.should validate_uniqueness_of :name }
    it { campaign.should serialize :channels }
    it { campaign.should validate_presence_of :comment }
    it { campaign.should validate_presence_of :program }
end

Alternatively, the code above allows you to leave the factory as it is and overwrite the program instance as needed.

  • Bonus *

In your specs, you're building objects with build + save!, while you should probably use FactoryGirl.create:

it "serializes column into Array" do
  campaign = FactoryGirl.create(:campaign, channels: [1,2,3])
  expect(campaign.channels).to eq [1, 2, 3]
end
1
On

I'm not sure if this is the same issue that you're having, but I was getting a similar error and the issue was that I had a Model.create! method call in the spec (which fails with a validation error message instead of failing silently). As soon as I removed the bang ! everything ran properly.

0
On

I dont know if this problem is solved or not.. considering it is a pretty old question. I struggled with this issue a lot myself. Adding my answer just for documentation.

I used sequence in my factory for ModelB, That did not help with the error. I also tried database_cleaner. Still no success.

The reason for the error is explained by Joshua Clayton in this post https://github.com/thoughtbot/factory_girl/issues/523

What I understood is that even when I used sequence, I was creating my ModelB somewhere in my tests previous to using the factory. Factorygirl was not able to keep track of the records with the sequence. Hence the name was already taken.

I suspect the following code was causing it all.

class ModelA
  before_create :create_model_b
...

As a quick and dirty way to solve the problem, I changed the factory for ModelB to offset the sequence by a large number.

FactoryGirl.define do
  factory :model_b do
    sequence(:name){|n| "name_#{n+1000}"} 
    ...

  end
end

It worked !! Seems to be pretty dumb a solution, but my specs are all green now :)

0
On

I had this problem because I had created a record with the same name before so I had to run:

rails db:setup

And then I got rid of it and it started working again.