Cole Bittel
Cole Bittel

Reputation: 2884

FactoryGirl to create duplicate instance

I'd like to test the uniqueness of entries on a city model.

class City < ActiveRecord::Base
  validates :name, uniqueness: { scope: :country_code,
    message: "This city has already been added" }
end

I need to create a spec for testing this validation. My thinking is that the test will be along these lines:

my_city = FactoryGirl.create(:city)

context "when inserting" do
  it "cannot have the same name and country code" do
    expect{create :my_city}.to raise_error(ActiveRecord::DuplicateRecord)
  end
end

However, I've been unable to figure out how to use FactoryGirl to create a duplicate instance of the my_city object. Simply using create :my_city as demonstrated in the above snippet results in:

ArgumentError: Factory not registered: my_city

Edit

This is what my City factory looks like:

# spec/factories/city_factory.rb
FactoryGirl.define do
  factory :city do
    name { Faker::Address.city}
    country { CountryCodes.find(Faker::Address.country_code)}
    latitude { Faker::Number.decimal(2, 6)}
    longitude { Faker::Number.decimal(2, 6)}
    population { Faker::Number.number([3, 6, 7].sample) }
    timezone { Faker::Address.time_zone }
  end
end

So simple to run create :city twice, will result in two completely different cities being inserted. I need to test the same city, twice.

Upvotes: 0

Views: 983

Answers (4)

sixty4bit
sixty4bit

Reputation: 7956

If you don't want to use shoulda-matchers you could do this simply by using FactoryGirl to create the first city, then building another city with the same name and country_code. Finally, test the error messages it produces when validity is checked:

city = create(:city)
invalid_city = City.new(country_code: city.country_code, name: city.name)


expect(invalid_city).not_to be_valid
expect(invalid_city.errors.full_messages).to include "This city has already been added"

Upvotes: 1

David Meza
David Meza

Reputation: 3180

Well, I cannot put it in better words than the error tells you. You do not have a factory called my_city registered. If you would like to create a duplicate record, you simply call the create method twice and pass the duplicate attributes that you'd like to test. For example, you can test this in the following ways:

it "cannot have the same name and country code" do
  first_city = create :city
  duplicate_city = build(:city, name: first_city.name, country_code: first_city.country_code)
  expect(duplicate_city).not_to be_valid
end


it "cannot have the same name and country code" do
  attrs = FactoryGirl.attributes_for :city
  first_city = create :city, attrs
  duplicate_city = build(:city, attrs)
  expect(duplicate_city).not_to be_valid
end


it "cannot have the same name and country code" do
  first_city = create :city
  expect{create (:city, name: first_city.name, country_code: first_city.country_code)}.to raise_error(ActiveRecord::DuplicateRecord)
end

Note that every time that you call the create method, you are creating a new record so the second time you call it there will already be an existing record in the database and the only attribute that will be different is the id. The exception to this is if you have declared a sequence in your factory or some other method call which will vary at time of test execution such as Date.today

Upvotes: 1

Martin Streicher
Martin Streicher

Reputation: 2031

In general, testing uniqueness is pretty simple. Let the shoulda matchers do the work for you. Once you have a factory you can do this...

specify { 
  create :city; 
  is_expected.to validate_uniqueness_of(:name).scoped_to(:country_code) }

You must have at least one record created. Then the shoulda matcher will use that record, read its attributes, and make a duplicate to test your validation.

Upvotes: 2

vicocas
vicocas

Reputation: 249

You can test your validation like this:

RSpec.describe City do
  let(:city) { create :city }
  subject    { city }

  it { should validate_uniqueness_of(:name).scoped_to(:country_code) }
end

But first you need to build your factory (on a separate file )for City like this:

FactoryGirl.define do
  factory :city do
    sequence(:name) { |n| Faker::Lorem.word + "(#{n})" }
  end
end

Upvotes: 2

Related Questions