I have 2 models sharing a simple belong_to/has_many relation: Room belongs to Building
I created a custom validator called total_number_rooms_limited_to_15 that ensures I can't create more than 15 rooms for a given Building.
class Room < ActiveRecord::Base
# -- Relationships --------------------------------------------------------
belongs_to :admin_user, :foreign_key => 'admin_user_id'
belongs_to :building, :foreign_key => 'building_id'
# -- Validations ----------------------------------------------------------
validates :room_filename,
presence: true
# associated models primary key validates
validates :admin_user_id,
presence: true
validates :building_id,
presence: true
validate :total_number_rooms_limited_to_15
private
def total_number_rooms_limited_to_15
errors[:base] << "There can't be more than 15 rooms. There are already 15 .
<br/>Please remove another one or drop trying adding this one.".html_safe
unless ( self.building.rooms.count < 15 )
end
But the problem is that after creating this new validator, all my "usual" basic tests fail.
require 'spec_helper'
RSpec.describe Room, type: :model do
before(:each) do
#attr = {
room_filename: "xyz"
}
end
# -- Models Tests --------------------------------------------------------
describe "tests on ST's models validations for room_filename" do
it { is_expected.to validate_presence_of(:room_filename) }
it { is_expected.not_to allow_value(" ").for(:room_filename) }
end
All give me the following error message:
1) Room tests on ST's models validations for room_filename should validate that :room_filename cannot be empty/falsy
Failure/Error:
errors[:base] << "There can't be more than 15 rooms. There are already 15 .
<br/>Please remove another one or drop trying adding this one.".html_safe unless ( self.building.rooms.count < 15 )
NoMethodError:
undefined method `rooms' for nil:NilClass
I tried adding inside #attr the attribute a associated "virtual" building but it not work out;, getting the same error message:
before(:each) do
#attr = {
room_filename: "xyz",
building_id: 1
}
ADDED INFO
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation, :except => %w(roles))
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
For custom validations you'll need to instantiate a new Room object in your tests. If you don't have something like factory_girl or fabrication in place to create objects for your test, you can do this:
before(:each) do
#admin_user = AdminUser.create!(...attributes)
#building = Building.create!(...attributes)
#room = Room.create!(building_id: #building.id, admin_user_id: #admin_user.id)
end
Then make sure you're calling your validation on the instance instead of your Room class:
def total_number_rooms_limited_to_15
errors[:base] << "There can't be more...".html_safe
unless ( building.present? && building.rooms.count < 15 )
end
Related
cannot seem to get my validators to work to ensure all attributes are present to allow a User to be created. Basic User with 2 attributes
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
end
tests to check that name and email are present when created. these #pass
RSpec.describe User, type: :model do
context 'validations' do
subject { FactoryGirl.build(:user) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_presence_of(:name) }
it "fails to create user unless both are present" do
expect { User.create(:name => 'jo bloggs1', :noemail => 'c#c.co')}.to raise_error(ActiveModel::UnknownAttributeError)
end
end
end
but if i try and create model with a missing attribute no error is raised
it "fails to create user unless both are present" do
expect { User.create(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
end
result
1) User validations fails to create user unless both are present
Failure/Error: expect { User.create(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
expected ActiveModel::MissingAttributeError but nothing was raised
# ./spec/models/user_spec.rb:12:in `block (3 levels) in <top (required)>'
fyi, FactoryGirl
FactoryGirl.define do
factory :user do
name "MyString"
email "MyString"
end
end
i have tried clever stuff like
class User < ApplicationRecord
# before_create :run_it
after_initialize :all_present?
validates :name, presence: true
validates :email, presence: true
private
def all_present?
if (#email.nil? || #name.nil?)
raise ActiveModel::MissingAttributeError.new()
end
end
end
but cannot seem to raise these manually...?
what am i doing wrong?
tx all
Ben
The problem is that there are 2 methods, create and create!. The first, create
The resulting object is returned whether the object was saved successfully to the database or not
Whereas with create!:
Raises a RecordInvalid error if validations fail, unlike Base#create
So, create fails silently and doesn't raise any exceptions, but you can still inspect the instance and see that it's a new record and has errors and such, and create! fails noisily, by raising the error you are expecting it to raise. In short, your test should be:
it "fails to create user unless both are present" do
expect { User.create!(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
end
I'm creating some test to test a controller and model. When I use FactoryGirl to create fake data I'm getting errors that the User (which the record belongs to) does not exist.
Here is my model composition.rb
class Composition < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :name, presence: true, uniqueness: {scope: :user_id}
end
Here is my FactoryGirl file composition.rb
require 'faker'
FactoryGirl.define do
factory :composition do
name { Faker::Name.name }
description { Faker::Lorem.words }
import_composition { Faker::Boolean.boolean }
import_composition_file { Faker::File.file_name('path/to') }
end
end
This is my the RSpec test that I have until this far
require 'rails_helper'
describe CompositionsController do
before(:each) do
#user = FactoryGirl.create(:user)
#group = FactoryGirl.create(:group)
sign_in #user
#composition = Composition.new(FactoryGirl.create(:composition), user_id: #user.id, group_id: #group.id)
end
describe "GET #index" do
it "renders the index template" do
get :index
expect(assigns(:composition).to eq(#composition))
expect(response).to render_template("index")
end
end
end
Right now I'm getting an error: Validation failed: User must exist, Group must exist
When I don't user FactoryGirl to create a record everything works fine.
Does any body have an suggestion why it's failing?
You don't need to pass FactoryGirl as a param to Model
#composition = FactoryGirl.create(:composition, user: #user, group: #group)
If you don't want to create the record but just want it to initialize, use build instead of create
#composition = FactoryGirl.build(:composition, user: #user, group: #group)
I am using FactoryGirl and populate unique attribute whenever the model is made. The problem with my model form is that there are only 4 different types available for form_type attribute. So I need to reset the sequence everytime I run tests. Like below, I user before do block to call FactoryGirl.reload. However, I saw an article saying it is anti-pattern to FactoryGirl. What is the best way to reset the sequence in FactoryGirl instead of calling FactoryGirl.reload before every test?
Here is my forms.rb Factorygirl file,
FactoryGirl.define do
factory :form do
association :user
sequence :form_type do |n|
Form.form_types.values[n]
end
end
end
Here is my form.rb model file:
class Form < ActiveRecord::Base
belongs_to :user, required: true
enum form_types: { :a => "Form A", :b => "Form B", :c => "Form C", :d => "Form D"}
validates :form_type, presence: true
validates :form_type, uniqueness: {scope: :user_id}
end
Here is my forms_controller_spec.rb file:
require 'rails_helper'
RSpec.describe FormsController, type: :controller do
login_user
let(:form) {
FactoryGirl.create(:form, user: #current_user)
}
let(:forms) {
FactoryGirl.create_list(:form , 3, user: #current_user)
}
let(:form_attributes) {
FactoryGirl.attributes_for(:form, user: #current_user)
}
describe "GET #index" do
before do
FactoryGirl.reload
end
it "loads all of the forms into #forms" do
get :index
expect(assigns(:forms)).to match_array(#forms)
end
end
end
Hm, it seems like the purpose of FG sequences is to ensure unique numbers. Or at least that's what I've used it for. You may be able to hack into FG if this is what you really want.
This question may help.
How can I reset a factory_girl sequence?
I am having a strange issue with RSPEC that I am unable to nail down.
The test fails but when I try in the browser the behavior works as expected.
Here is the model code so far:
class QueueItem < ActiveRecord::Base
belongs_to :video
belongs_to :user
validates_presence_of :user_id, :video_id
validates_uniqueness_of :video_id, scope: :user_id
validates_numericality_of :position, only_integer: true
delegate :category, to: :video
delegate :title, to: :video, prefix: :video
...
end
And here is the controller code so far:
class QueueItemsController < ApplicationController
...
before_action :require_user
def update_queue
begin
ActiveRecord::Base.transaction do
queue_items_params.each do |queue_item_input|
queue_item = QueueItem.find(queue_item_input[:id])
queue_item.update!(position: queue_item_input[:position])
end
end
rescue ActiveRecord::RecordInvalid
flash[:danger] = "Your queue was not updated, make sure you only use numbers to set the position"
redirect_to queue_items_path
return
end
current_user.queue_items.each_with_index { |queue_item, i| queue_item.update(position: i+1 )}
redirect_to queue_items_path
end
private
def queue_items_params
params.permit(queue_items:[:id, :position])[:queue_items]
end
...
end
And now the controller spec that fails:
describe "POST #update" do
context 'when user is signed in' do
let(:user) { Fabricate(:user) }
before { session[:user] = user.id }
context 'with invalid attributes' do
it "does not update the queue items position" do
queue_item1 = Fabricate(:queue_item, user: user, position: 1)
queue_item2 = Fabricate(:queue_item, user: user, position: 2)
post :update_queue, queue_items: [{id: queue_item1.id, position: 6}, {id: queue_item2.id, position: 2.2}]
expect(queue_item1.reload.position).to eq(1)
expect(queue_item2.reload.position).to eq(2)
end
end
end
end
And the error message:
Failures:
1) QueueItemsController POST #update when user is signed in with invalid attributes does not update the queue items position
Failure/Error: expect(queue_item1.reload.position).to eq(1)
expected: 1
got: 6
(compared using ==)
I don't understand why the spec fails but in the browser it works.
What am I missing?
Thank you very much
I actually found the issue!
It was due to DatabaseCleaner gem which I am using in my rspec_helper.rb setup.
The following setup did not work:
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
I fixed it by changing it to:
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :deletion
end
Given I have the following class
class listing > ActiveRecord::Base
attr_accessible :address
belongs_to :owner
validates :owner_id, presence: true
validates :address, presence: true
end
Is there a way I can get away with not having to keep associating an owner before I save a listing in my tests in /spec/models/listing_spec.rb, without making owner_id accessible through mass assignment?
describe Listing do
before(:each) do
#owner = Factory :owner
#valid_attr = {
address: 'An address',
}
end
it "should create a new instance given valid attributes" do
listing = Listing.new #valid_attr
listing.owner = #owner
listing.save!
end
it "should require an address" do
listing = Listing.new #valid_attr.merge(:address => "")
listing.owner = #owner
listing.should_not be_valid
end
end
No need to use factory-girl (unless you want to...):
let(:valid_attributes) { address: 'An Address', owner_id: 5}
it "creates a new instance with valid attributes" do
listing = Listing.new(valid_attributes)
listing.should be_valid
end
it "requires an address" do
listing = Listing.new(valid_attributes.except(:address))
listing.should_not be_valid
listing.errors(:address).should include("must be present")
end
it "requires an owner_id" do
listing = Listing.new(valid_attributes.except(:owner_id))
listing.should_not be_valid
listing.errors(:owner_id).should include("must be present")
end
There is if you use factory-girl
# it's probably not a good idea to use FG in the first one
it "should create a new instance given valid attributes" do
listing = Listing.new #valid_attr
listing.owner = #owner
listing.property_type = Factory(:property_type)
listing.save!
end
it "should require an address" do
# But here you can use it fine
listing = Factory.build :listing, address: ''
listing.should_not be_valid
end
it "should require a reasonable short address" do
listing = Factory.build :listing, address: 'a'*245
listing.should_not be_valid
end
I hate to be the voice of dissent here, but you shouldn't be calling save! or valid? at all in your validation spec. And 9 times out of 10, if you need to use factory girl just to check the validity of your model, something is very wrong. What you should be doing is checking for errors on each of the attributes.
A better way to write the above would be:
describe Listing do
describe "when first created" do
it { should have(1).error_on(:address) }
it { should have(1).error_on(:owner_id) }
end
end
Also, chances are you don't want to be checking for the presence of an address, you want to check that it is not nil, not an empty string, and that it is not longer than a certain length. You'll want to use validates_length_of for that.