rspec validates_presence_of polymorphic attributes ruby on rails - ruby-on-rails

I am writing an rspec for an address model that has a polymorphic attribute called :addressable. I am using factory girl for testing.
This model has no controller because I do not wish to create a standalone address but that doesnt stop the rspec from creating a standalone address. My model, factory and rspec are
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
belongs_to :state
attr_accessible :street, :city, :state_id, :zip
validates_presence_of :street, :city, :zip, :state
validates_associated :state
end
Factory.define :address do |address|
address.street "1234 Any st"
address.city "Any City"
address.zip "90001"
address.association :state
end
describe Address do
before(:each) do
state = Factory(:state)
#attr = {:street => "1234 Any St", :city => "Any City", :zip => "Any Zip", :state_id => state.id}
end
it "should create a new address given valid attributes" do
Address.create!(#attr).valid?.should be_true
end
end
This rspec test will create an address with addressable_id and addressable_type of NULL and NULL but if I create from any other model, lets say a users model or a store model it will put in the correct data. So what I'm trying to figure out and research, unsuccessfully, is how do you validate_presence_of a polymorphic attribute and test it through a factory.
Sorry for being long winded but I havent been able to find a solution for this in the last couple of hours and I'm starting to consider I might be approaching this the wrong way.

I don't see anything in your spec that associates the address with an addressable. You are creating an address, but it's just out in space -- what is it connected to? NULL for addressable_id and addressable_type seems correct.

Related

Rspec: How to create mock association

I have following class:
class Company < ActiveRecord::Base
validates :name, :presence => true
has_many :employees, :dependent => :destroy
end
class Employee < ActiveRecord::Base
validates :first_name, :presence => true
validates :last_name, :presence => true
validates :company, :presence => true
belongs_to :company
end
I am writing test for Employee class, so I am trying to create double for Company which will be used by Employee.
Below is the snippet for my Rspec
let(:company) { double(Company) }
let(:employee) { Employee.new(:first_name => 'Tom', :last_name => 'Smith', :company => company) }
context 'valid Employee' do
it 'will pass validation' do
expect(employee).to be_valid
end
it 'will have no error message' do
expect(employee.errors.count).to eq(0)
end
it 'will save employee to database' do
expect{employee.save}.to change{Employee.count}.from(0).to(1)
end
end
I am getting following error message for all of my 3 tests
ActiveRecord::AssociationTypeMismatch:
Company(#70364315335080) expected, got RSpec::Mocks::Double(#70364252187580)
I think the way I am trying to create double is wrong. Can you please guide me how to create a double of Company which can be used by Employee as their association.
I am not using FactoryGirl.
Thanks a lot.
There is not really a great way to do this, and I'm not sure you need to anyway.
Your first two tests are essentially testing the same thing (since if the employee is valid, employee.errors.count will be 0, and vice versa), while your third test is testing the framework/ActiveRecord, and not any of your code.
As other answers have mentioned, Rails wants the same classes when validating in that way, so at some point you'll have to persist the company. However, you can do that in just one test and get the speed you want in all the others. Something like this:
let(:company) { Company.new }
let(:employee) { Employee.new(:first_name => 'Tom', :last_name => 'Smith', :company => company) }
context 'valid Employee' do
it 'has valid first name' do
employee.valid?
expect(employee.errors.keys).not_to include :first_name
end
it 'has valid last name' do
employee.valid?
expect(employee.errors.keys).not_to include :last_name
end
it 'has valid company' do
company.save!
employee.valid?
expect(employee.errors.keys).not_to include :company
end
end
And if you really want to keep your third test, you can either include company.save! in your it block, or disable validation (though, again, what are you even testing at that point?):
it 'will save employee to database' do
expect{employee.save!(validate: false)}.to change{Employee.count}.from(0).to(1)
end
There is already similar question on SO (Rspec Mocking: ActiveRecord::AssociationTypeMismatch). I think you can't get away from using real AR objects 'cause it seems that Rails checks exact class of association object and double is an instance of some absolutely different class. Maybe you could stub some inner Rails' methods to skip that check but I think it's an overhead.

FactoryGirl Name "can't be blank" VS "already taken"

I'm trying to run some tests on a model "Click".
# models/click_spec.rb
describe Click do
it "should have a valid constructor" do
FactoryGirl.create(:click).should be_valid
end
end
The objective is that the model uses two tables that have the same country. I don't want to use a sequence for the country (as I found for Emails).
But it raise this error:
Validation failed: Name has already been taken, Slug has already been taken
The problem is that it seems that it create twice the country [name:"United States", slug:"us"]
Here's the factories used.
# factories/countries.rb
FactoryGirl.define do
factory :country do
name "United States"
slug "us"
end
end
# factories/offers.rb
FactoryGirl.define do
factory :offer do
association :country, factory: :country
# Other columns
end
end
# factories/users.rb
FactoryGirl.define do
factory :user do
association :country, factory: :country
# Other columns
end
end
# factories/clicks.rb
FactoryGirl.define do
factory :click do
association :offer, factory: :offer
association :user, factory: :user
# Other columns
end
end
and the model of Country:
class Country < ActiveRecord::Base
validates :name, :slug,
presence: true,
uniqueness: { case_sensitive: false }
validates :slug,
length: { is: 2 }
end
I've tried to change the association strategy to something like this:
association :country, factory: :country, strategy: :build
But it raise this error:
Validation failed: Country can't be blank
Any idea?
Thanks,
As per the shared code,
when you call FactoryGirl.create(:click),
it will go to execute factory :click where it finds association :offer, factory: :offer
which in turn calls factory: :offer where you create a country with name "United States" and slug "us" for the first time.
Again, in factory :click, it finds association :user, factory: :user which in turn calls factory: :user where you create a country again with the same name "United States" and slug "us" for the second time.
Issue #1: Validation failed: Name has already been taken, Slug has already been taken
The above error is because of the uniqueness constraint on Country model for name and slug.
Issue #2: Validation failed: Country can't be blank
When you do association :country, factory: :country, strategy: :build then strategy: :build only creates an instance of Country, it does create a record in database.
The Country can't be blank error is because you didn't create a country record in the database for user and offer. And you must be having a validation presence: true in these two models for country OR schema level check of not null.

FactoryGirl Associations

I have a pretty basic rails app that I'm working on.
Parts are in certain states (state_id), they are created by a user (user_id) and have an associated type with them (type_id).
Trying to create a factory for part, I have:
FactoryGirl.define do
factory :part do
name "blah"
association :state_id, factory: :state
association :user_id, factory: :user
association :techtype_id, factory: :techtype
end
factory :state do
name "blah"
end
factory :user do
login "blah"
end
factory :techtype do
name "blah"
example "bleh"
end
end
Yet FactoryGirl.create(:part) doesn't seem to work:
2.0.0p353 :001 > part = FactoryGirl.create(:part)
[SQL insert for State, User, and Techtype outputs here and succeeds, then...]
ActiveRecord::RecordInvalid: Validation failed:
State can't be blank, Techtype can't be blank, User can't be blank
I've tried removing the _id attribute (i.e. association :state, factory: :state) but that doesnt work either, I just get a NoMethodError: undefined method 'state=' for #<Part:0x007fa3e8e798a0>. I've also just tried using the short form association (i.e. state instead of association :state_id, factory: :state) but I get the same NoMethodError.
Your model should look like this
class Part < ActiveRecord::Base
belongs_to :state
belongs_to :user
belongs_to :techtype
end
And your factory like this
factory :part do
name "blah"
association :state
association :user
association :techtype
end

FactoryGirl build strategy with nested associations

Is it possible to preserve the build strategy when I have a factory for a model that has an association to a second model, which itself has an association to a third model?
In the example below, a Post is associated with a User, and a User is associated with a City. Even when :strategy => :build is used for all associations, post.user and post.user.city end up getting saved to the database. In the interests of a speedy test suite, can I prevent these database writes from happening?
Factory.define do
factory :user do
name "A User"
association :city, :strategy => :build
end
factory :city do
name "A City"
end
factory :post do
title "A Post"
body "Some text here"
association :user, :strategy => :build
end
end
post = FactoryGirl.build(:post)
post.new_record? # True
post.user.new_record? # False
post.user.city.new_record? # False
Have you tried the alternative block syntax?
Factory.define do
factory :user do
name "A User"
city { |city| city.association :city, :strategy => :build }
end
factory :city do
name "A City"
end
end
It looks like FactoryBot (formerly FactoryGirl) added use_parent_strategy as a configuration option in v4.8.0. It is turned off by default, to turn it on add the following to your spec/rails_helper:
FactoryGirl.use_parent_strategy = true
Relevant pull request on the factory_bot repo: https://github.com/thoughtbot/factory_bot/pull/961
As #messanjah said, however for older versions (< v4.8.0) you can do the following:
association :user, :strategy => #build_strategy.class

FactoryGirl belongs_to association

I have a factory where I define a location in factories/locations.rb. I'm using Mongoid and Rails 3.1.1 with ruby 1.9.3.
FactoryGirl.define do
factory :location do
name Faker::Name.name
description "Down by the river"
end
end
And then I want to define a fitness camp which belongs_to a location (and therefore has a location_id attribute).
FactoryGirl.define do
factory :fitness_camp do
title "Parkour"
association :location_id, :factory => :location
end
end
This works but, is the result of my hacking, not what I read in the docs. From the getting started guide ( https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md) it seems this should be as simple as:
factory :fitness_camp do
title "Parkour"
location
end
Am I missing something? Does this indicate my models might not be configured correctly?
Thanks!
Tim
I was an idiot -- I had validates_numericality_of :location_id
class FitnessCamp
include Mongoid::Document
field :title, :type => String
belongs_to :location
validates_presence_of :location_id, :title
validates_numericality_of :location_id
Mad props to Radar (Ryan Bigg) for helping me through this.

Resources