Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rspec: Validation failed: Name has already been taken

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
like image 364
shroy Avatar asked Jun 18 '15 23:06

shroy


2 Answers

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
like image 85
ivan Avatar answered Oct 22 '22 20:10

ivan


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
like image 21
lucke84 Avatar answered Oct 22 '22 20:10

lucke84