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.
Related
error message:
Failure/Error: let(:rubric_in_grenoble){ create(:rubric_in_grenoble) }
ActiveRecord::RecordInvalid:
Validation failed: Name has already been taken
So, as I suppose, the :rubric factory is tried to be created second
time (because the :rubric_in_grenoble should have association to the
same culture as :rubric_in_wroclaw). How should I change this code to
not create again same factory model, but to associate it with the
existing one?
I use Database Cleaner
I'm writing test to the model Population.
class Population < ApplicationRecord
belongs_to :province
belongs_to :culture
validates_presence_of :province, :culture
#some methods
end
It's associated to Province
class Province < ApplicationRecord
enum terrain: [:sea, :plains, :hills, :mountains]
validates :terrain, presence: true
validates :name,
presence: true,
uniqueness: true,
format: {
with: /\A[A-Za-z ]{3,30}\z/,
message: "province name has to contain letters and spaces only"
}
has_many :populations
##some methods
end
And to Culture
class Culture < ApplicationRecord
before_validation :downcase_name
validates :name, presence: true,
format: {
with: /\A[a-z]{3,20}\z/,
message: "culture name has to contain letters only"
},
length: { minimum: 3, maximum: 20 }
##many-to-many associations to different model - tested correctly
has_many :populations
end
In my test suite I use FactoryGirl and RSpec. Rails 5.0.1. I've got also simplecov installed (I'm 99.99% sure it's not interfering, but it's better to highlight this) and DatabaseCleaner works properly.
Culture and Province models are tested and everything's fine. In my FactoryGirl file for Populations there is:
FactoryGirl.define do
factory :rubric_in_wroclaw, class: "Population" do
association :province, factory: :wroclaw
association :culture, factory: :rubric
quantity 10
end
factory :javan_in_wroclaw, class: 'Population' do
association :province, factory: :grenoble
association :culture, factory: :javan
quantity 15
end
factory :rubric_in_grenoble, class: 'Population' do
association :province, factory: :grenoble
association :culture, factory: :rubric
quantity 25
end
end
And in my population_spec.rb file I've got:
require 'rails_helper'
RSpec.describe Population, type: :model do
let(:rubric_in_wroclaw){ create(:rubric_in_wroclaw) }
let(:javan_in_wroclaw){ create(:javan_in_wroclaw) }
let(:rubric_in_grenoble){ create(:rubric_in_grenoble) }
let(:pops){ [ rubric_in_wroclaw, javan_in_wroclaw, rubric_in_grenoble] }
describe '#validations' #shoulda-matchers validations - GREEN
describe '#methods' do
describe '#global_population' do
context 'without any pop' #without any fixture created
context 'with many pops' do
before { pops } <--- there is an error
it 'is equal to 50' do
expect(Population.global_population).to eq 50
end
end
end**
end
describe '#factories' #each factory is tested separately - everything's ok
end
Repeated question (because post is very long)
So, as I suppose, the :rubric factory is tried to be created second
time (because the :rubric_in_grenoble should have association to the
same culture as :rubric_in_wroclaw). How should I change this code to
not create again same factory model, but to associate it with the
existing one?
Short Answer:
Probably you are giving duplicate value to your Province.name field. The sequence should fix the problem.
Long Answer:
Let's look at the error again.
Failure/Error: let(:rubric_in_grenoble){ create(:rubric_in_grenoble) }
ActiveRecord::RecordInvalid:
Validation failed: Name has already been taken
The exception tells us we are trying to create a record with a name twice also
I can see a uniqueness validation in Province.name.
class Province < ApplicationRecord
enum terrain: [:sea, :plains, :hills, :mountains]
validates :terrain, presence: true
validates :name,
presence: true,
uniqueness: true,
format: {
with: /\A[A-Za-z ]{3,30}\z/,
message: "province name has to contain letters and spaces only"
}
has_many :populations
##some methods
end
You didn't paste the Province factory, but I expect factory is something like this:
FactoryGirl.define do
factory :my_factory, class: "Province" do
name 'blabla'
# other fields
end
end
So we have to use sequence instead of static value.
FactoryGirl.define do
factory :grenoble, class: "Province" do
sequence(:name) { |i| "BLABLA#{(i+64).chr}" }
# other fields
end
end
Assign same Province
RSpec.describe Population, type: :model do
let(:my_province){ create(:my_province) }
let(:rubric_in_wroclaw){ create(:rubric_in_wroclaw, province: :my_province) }
let(:javan_in_wroclaw){ create(:javan_in_wroclaw, province: :my_province) }
end
I am totally new to Rails and testing and I wrote this model:
class KeyPerformanceInd < ActiveRecord::Base
#attr_accessible :name, :organization_id, :target
include ActiveModel::ForbiddenAttributesProtection
belongs_to :organization
has_many :key_performance_intervals, :foreign_key => 'kpi_id'
validates :name, presence: true
validates :target, presence: true
validates :organization_id, presence: true
end
My question is for a model like that, what are some RSpec tests that I should write?
Somethings like this? Or there are moe things to do? I hear about FactoryGirl, Is that something I need for testing this model or that is for testing stuff in the controller?
Describe KeyPerformanceInd do
it {should belong_to(:key_performance_interval)}
end
In this case, you don't need to do more, and you can also use the shoulda-matchers gem to make your code really clean :
it { should belong_to(:organization) }
it { should have_many(:key_performance_intervals) }
it { should validate_presence_of(:name) }
it { should validate_presence_of(:target) }
it { should validate_presence_of(:organization_id) }
And this is it.
You don't need FactoryGirl in this case, which is used to create valid and re-usable objects. But you could use factories in your model test. A simple example :
Your factory :
FactoryGirl.define do
factory :user do
first_name "John"
last_name "Doe"
end
end
Your test :
it "should be valid with valid attributes" do
user = FactoryGirl.create(:user)
user.should be_valid
end
Check the Factory Girl documentation to have more informations.
I have a Customer model that has_many polymorphic Address objects, like so:
Customer Model:
class Customer < ActiveRecord::Base
has_many :mailing_addresses, :as => :addressable, :class_name => 'Address', dependent => :destroy
accepts_nested_attributes_for :mailing_addresses
validates :mailing_addresses, :presence => true
validates_associated :mailing_addresses
end
Address Model:
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
validate :validate_quota
private
def validate_quota
case addressable_type
when "Customer"
customer = Customer.find(addressable_id)
if customer.mailing_addresses.size >= 3
puts "Adding too many records"
errors.add(:addressable, "Too many records")
end
end
end
I am also using RSpec to test that the quota constraint is respected. So this spec passes, for example:
it "observes quota limit" do
customer = FactoryGirl.create(:customer, :number_of_mailing_addresses => 3)
expect {
address = FactoryGirl.create(:mailing_address, :addressable => customer)
}.to raise_error
customer.mailing_addresses.count.should eq(3)
end
Which is good. However, this fails horribly:
it "fails add if already 3 addresses" do
customer = FactoryGirl.create(:customer, :number_of_mailing_addresses => 3)
expect {
customer.mailing_addresses.create( FactoryGirl.attributes_for(:mailing_address).except(:addressable) )
}.to raise_error
end
I can even see in the spec output that the Address validation is failing, but for some reason it doesn't raise an error in "customer.mailing_addresses.create()", nor does that failed validation prevent the 4th Address model from saving to the database.
What am I missing?
Ah, I finally figured it out! I needed to change create to create! in my last spec in order to make the failing validation actually throw an error.
I'm still pretty new to testing in Rails 3, and I use RSpec and Remarkable. I read through a lot of posts and some books already, but I'm still kind of stuck in uncertainty when to use the association's name, when its ID.
class Project < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
end
Because of good practice, I want to protect my attributes from mass assignments:
class Task < ActiveRecord::Base
attr_accessible :project # Or is it :project_id??
belongs_to :project
end
First of all, I want to make sure that a project never exists without a valid task:
class Task < ActiveRecord::Base
validates :project, :presence => true # Which one is the...
validates :project_id, :presence => true # ...right way to go??
end
I also want to make sure that the assigned project or project ID is always valid:
class Task < ActiveRecord::Base
validates :project, :associated => true # Again, which one is...
validates :project_id, :associated => true # ...the right way to go?
end
...and do I need the validation on :presence when I use :associated??
Thanks a lot for clarifying, it seems that after hours of reading and trying to test stuff using RSpec/Shoulda/Remarkable I don't see the forest because of all the trees anymore...
This seems to be the right way to do it:
attr_accessible :project_id
You don't have to put :project there, too! It's anyway possible to do task.project=(Project.first!)
Then check for the existence of the :project_id using the following (:project_id is also set when task.project=(...) is used):
validates :project_id, :presence => true
Now make sure than an associated Project is valid like this:
validates :project, :associated => true
So:
t = Task.new
t.project_id = 1 # Value is accepted, regardless whether there is a Project with ID 1
t.project = Project.first # Any existing valid project is accepted
t.project = Project.new(:name => 'valid value') # A new valid project is accepted
t.project = Project.new(:name => 'invalid value') # A new invalid (or an existing invalid) project is NOT accepted!
It's a bit a pity that when assigning an ID through t.project_id = it's not checked whether this specific ID really exists. You have to check this using a custom validation or using the Validates Existence GEM.
To test these associations using RSpec with Remarkable matchers, do something like:
describe Task do
it { should validate_presence_of :project_id }
it { should validate_associated :project }
end
validates :project, :associated => true
validates :project_id, :presence => true
If you want to be sure that an association is present, you’ll need to
test whether the foreign key used to map the association is present,
and not the associated object itself.
http://guides.rubyonrails.org/active_record_validations_callbacks.html
attr_accessible :project_id
EDIT: assuming that the association is not optional...
The only way I can get it to thoroughly validate is this:
validates_associated :project
validates_presence_of :project_id,
:unless => Proc.new {|o| o.project.try(:new_record?)}
validates_presence_of :project, :if => Proc.new {|o| o.project_id}
The first line validates whether the associated Project is valid, if there is one. The second line insists on the project_id being present, unless the associated Project exists and is new (if it's a new record, it won't have an ID yet). The third line ensures that the Project is present if there is an ID present, i.e., if the associated Project has already been saved.
ActiveRecord will assign a project_id to the task if you assign a saved Project to project. If you assign an unsaved/new Project to project, it will leave project_id blank. Thus, we want to ensure that project_id is present, but only when dealing with a saved Project; this is accomplished in line two above.
Conversely, if you assign a number to project_id that represents a real Project, ActiveRecord will fill in project with the corresponding Project object. But if the ID you assigned is bogus, it will leave project as nil. Thus line three above, which ensures that we have a project if project_id is filled in -- if you supply a bogus ID, this will fail.
See RSpec examples that test these validations: https://gist.github.com/kianw/5085085
Joshua Muheim's solution works, but I hate being not be able to simply link a project to a task with an id like this:
t = Task.new
t.project_id = 123 # Won't verify if it's valid or not.
So I came up with this instead:
class Task < ActiveRecord:Base
belongs_to :project
validates :project_id, :presence => true
validate :project_exists
private
def project_exists
# Validation will pass if the project exists
valid = Project.exists?(self.project_id)
self.errors.add(:project, "doesn't exist.") unless valid
end
end
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.