Why does create from factory_girl throw error? - ruby-on-rails

When i don't use factory_girl
I can create objects in my rspec test as
u = User.create(email: 'as#wewe.com', password: 'asdqweqweqe', admin: true, firstname: 'qwe', lastname: 'wer', grade: 5, section: 'w')
expect(u.errors[:email]).to_not be_empty
expect(u.errors[:role]).to_not be_empty
This way i can check for validation error i.e
expect(u.errors[:role]).to_not be_empty
but if i use factory girl
factory :user_no_role, class: User do
email 'asd#we.com'
password 'asasdasdasd'
admin true
firstname 'qwe'
lastname 'wer'
grade 5
section 'w'
end
it 'role should be present' do
u = create(:user_no_role)
expect(u.errors[:role]).to_not be_empty
end
I get the following error
User role should be present
Failure/Error: u = create(:user_no_role)
ActiveRecord::RecordInvalid:
Validation failed: Role can't be blank
does factory_girl create method throw error? if so how can i test for validation error with rspecs as done above? Thanks!

Create will throw validation exceptions, since you are trying to save them immediately.
If you would just build one, like:
u = User.new(...)
u.valid?
=> false
u.errors # is not empty
This should apply to FactoryGirl.build too
it 'role should be present' do
u = build(:user_no_role)
expect(u.errors[:role]).to_not be_empty
end
BTW: User.create will not throw exceptions - but User.create! does.
See the ActiveRecord docs of create! and FactoryGirl docs of build for more infos.

Related

Validation on rails that permits first_name or last_name to be null but not both

class Profile < ApplicationRecord
belongs_to :user
validate :first_or_last_name_null
def first_or_last_name_null
if first_name.nil? && last_name.nil?
errors.add(:base, "either first_name or last_name must be present!")
end
end
I don't know what is wrong in my lines of code to get the following error from rspec..
Assignment rq11 Validators: allows a Profile with a null first name when last name present
Failure/Error: expect(Profile.new(:first_name=>nil, :last_name=>"Smith", :gender=>"male")).to be_valid
expected `#<Profile id: nil, gender: "male", birth_year: nil, first_name: nil, last_name: "Smith", user_id: nil, created_at: nil, updated_at: nil>.valid?` to return true, got false
The spec file has the following ..
context "rq11" do
context "Validators:" do
it "does not allow a User without a username" do
expect(User.new(:username=> "")).to_not be_valid
end
it "does not allow a Profile with a null first and last name" do
expect(Profile.new(:first_name=>nil, :last_name=>nil, :gender=>"male")).to_not be_valid
end
it "allows a Profile with a null first name when last name present" do
expect(Profile.new(:first_name=>nil, :last_name=>"Smith", :gender=>"male")).to be_valid
end
it "allows a Profile with a null last name when first name present" do
expect(Profile.new(:first_name=>"Joe", :last_name=>nil, :gender=>"male")).to be_valid
end
it "does not allow a Profile with a gender other than male or female " do
expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"neutral")).to_not be_valid
end
it "does not allow a boy named Sue" do
expect(Profile.new(:first_name=>"Sue", :last_name=>"last", :gender=>"male")).to_not be_valid
end
it "allows a Profile with gender male" do
expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"male")).to be_valid
end
it "allows a Profile with gender female" do
expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"female")).to be_valid
end
end
end
While Roman's answer is correct, I would like to add more details and more options to solve the issue.
Your Profile belong_to :user. Per default belongs_to associations require the associated object to exist. In this case, there must be a user associated with a profile otherwise the profile will not be valid.
You have three options to fix this issue, depending on your use-case:
Make the association optional, read about optional belongs_to associations in the Rails Guides. This obviously is only an option if it makes sense in the context of your application that there is no need for an association to always exist.
belongs_to :user, optional: true
optional: true disables the built-in validation.
You make sure that each profile in your spec has always a valid user assigned. Something like this might work:
let(:user) { User.find_or_create_by(username: 'test_user') }
it "does not allow a Profile with a null first and last name" do
expect(Profile.new(user: user, first_name: nil, last_name: nil, gender: "male")).to_not be_valid
end
You do not only test if an instance is valid, but instead, if there is an expected, specific error
it "does not allow a Profile with a null first and last name" do
profile = Profile.new(:first_name=>nil, :last_name=>nil, :gender=>"male")
profile.valid? # trigger validations
# with all Rails versions
expect(profile.errors[:base]).to include "either first_name or last_name must be present!"
# or with Rails >= 6:
expect(profile.errors).to be_of_kind(:base, "either first_name or last_name must be present!")
end
I think it is not valid because user_id is empty. By default rails validates presence of associations as I remember. Add user_id to all profiles and it should be ok

(Rails) update_attributes throws error (needing a password) during integration test that doesn't use password

I'm writing a Rails integration test that checks that the user's title saves. The title has one validation: it has to be no more than 255 characters. But #user.update_attributes!(title: params[:title]) is throwing the error "Password must have at least 6 characters." But...I'm not updating the password or anything other than the title. So how do I save this attribute with its own validation and not worry about the password?
Test:
test "profile submits new title and description successfully" do
log_in_as(#non_admin)
get user_path(#non_admin)
assert_nil #non_admin.title
post "/users/#{#non_admin.id}/update_description",
{ title: "I am a man of constant sorrow." }
user = assigns(:user)
user.reload.title
assert user.title == "I am a man of constant sorrow."
assert_template 'users/show'
assert flash[:success]
end
Controller method (not finished, but you'll get the idea). Note, it's the update_attributes! call that throws the password validation error.
# Handles user's posted title and description.
def update_description
#user = User.find(params[:id])
# Check if title is present. If so, attempt to save, load flash, and reload.
if #user.update_attributes!(title: params[:title])
flash[:success] = "Saved title. "
# if unable, set error flash and reload.
else
flash[:warning] = "Unable to save."
end
# Same logic as before, now for description.
# Make sure two different [:success] flashes work! Probably not!
redirect_to user_path(#user)
end
Validations:
validates :password, length: { minimum: 6,
message: "must have at least 6 characters" }
validates :title, length: { maximum: 255 }
Here's the test error:
23:08:51 - INFO - Running: test/integration/users_show_test.rb
Started
ERROR["test_profile_submits_new_title_and_description_successfully", UsersShowTest, 2017-10-23 01:06:11 -0400]
test_profile_submits_new_title_and_description_successfully#UsersShowTest (1508735171.57s)
ActiveRecord::RecordInvalid: ActiveRecord::RecordInvalid: Validation failed: Password must have at least 6 characters
app/controllers/users_controller.rb:71:in `update_description'
test/integration/users_show_test.rb:22:in `block in <class:UsersShowTest>'
app/controllers/users_controller.rb:71:in `update_description'
test/integration/users_show_test.rb:22:in `block in <class:UsersShowTest>'
In case it's relevant, here's the fixture that is loaded as #non_admin:
archer:
name: Sterling Archer
email: duchess#example.gov
password_digest: <%= User.digest('Jsdfuisd8f') %>
activated: true
activated_at: <%= Time.zone.now %>
I'm a Rails noob so it's probably something basic. Thanks in advance...
UPDATE: See discussion with kasperite below. I simply needed to add on: create to my password validation.
Calling update_attributes! will triggers save!, which in turn triggers validation on the model. And since you don't provide password, it will throw the exception.
You can either do update_attribute(:title, params[:title]) which bypass validation
or this:
#user.title = params[:title]
#user.save!(validation: false)
See: http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update-21

Rspec rails on rails validation

validate :cannot_modify_if_locked, on: :update
def cannot_modify_if_locked
if self.locked
errors.add(:locked_at, "#{self.locked_at} and cannot be modified")
false
end
end
RSPEC
consent_form = build(:consent_form, { locked_at: Date.today })
expect(consent_form.).to eq true
This test is not good.
how to write this test? i need to check with rspec this validation on update
Within your spec you can use the let helper to memoize an object, which you could use to "recreate" an update which wouldn't have success, that's to say, must be invalid.
Suppose you have your model, you can create a record which will have their attributes and also the locked as true, and the locked_at attribute to show the date in which it was locked:
RSpec.describe SomeModel, type: :model do
let(:some_object) { SomeModel.new(locked: true, locked_at: Time.now) }
Then you can access the object "created" with let and try to perform an update to it, then you can use expect to validate it won't be valid:
it 'invalid if object is locked' do
some_object.update(some_atttribute: 'Some new attribute')
expect(some_object).to_not be_valid
end
And also you could check for the validation error message:
expect(some_object.errors[:locked_at].first).to eq("#{task.locked_at} and cannot be modified")

How to output values of rspec subject to debug?

I can't figure out why my validation is failing. How can I output the value of my rspec subject i.e. model built from factory_girl
I am doing this test which is failing:
let(:factory_instance) { build(:user)}
it { should allow_value("abc123").for(:password) }
Test is failing, it has some errors like:
"can't be blank" (attribute: email, value: nil)
I want to output the value user that is loaded using factory_girl to see what is going on.
Change your it statement to a do block and then on the first line just log out user:
it "should do something" do
p "#{user.inspect}"
# use expect here
user.email = "valid#email.com"
user.password = "abc123"
expect(user.save).to eq(true)
end

RSpec - Testing errors for 2 attributes that share the same validation(s)

I have two attributes within my model that share the same validations
validates :first_name, :last_name, length: {minimum: 2}
Right now I have the :first_name attribute tested as follows:
RSpec.describe User, :type => :model do
it 'is invalid if the first name is less than two characters'
user = User.create(
first_name: 'a'
)
expect(user).to have(1).errors_on(:first_name)
end
For the sake of a developer who isn't familiar with how I've setup my model I wanted to explicitly state the two attributes' relationship with something like this:
it 'is invalid if the first name and/or last name has less than two characters'
user = User.create(
first_name: 'a',
last_name: 'b
)
expect(user).to have(1).errors_on(:first_name, :last_name)
Obviously this throws the error:
wrong number of arguments (2 for 0..1)
The same thing would apply if I had instituted 2 two validations:
validates :first_name, :last_name, length: {minimum: 2}, format: {with: /^([^\d\W]|[-])*$/}
And try to test for 2 errors:
it 'is invalid if the first name and/or last name has less than two characters and has special characters'
user = User.create(
first_name: '#',
last_name: '#
)
expect(user).to have(2).errors_on(:first_name, :last_name)
In RSpec 3.x, you can compound expectations with .and:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name).and have(1).errors_on(:last_name)
end
Check out the rspec-expectations documentation for more info.
For RSpec 2.x, you'll need to do one of these:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name) && have(1).errors_on(:last_name)
end
# or
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to have(1).errors_on(:first_name)
expect(user).to have(1).errors_on(:last_name)
end
It's not as pretty, but it should work.
EDIT:
OP was using rspec-collection_matchers gem. That gem's custom matchers do not include RSpec 3 mixin module RSpec::Matchers::Composable, so the #and method goes unrecognized.
There are a few things to do to circumvent this issue. The easiest is to use the && technique above (in my RSpec 2.x suggestions). To use only RSpec 3 matchers, you need to use be_valid:
it 'is invalid if the first name and/or last name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'b')
expect(user).to_not be_valid
end
Of course, this does not distinguish between first_name errors and last_name errors as was originally intended. To do that with the be_valid matcher, you'd have to break the test into two tests:
it 'is invalid if the first name has less than two characters' do
user = User.create(first_name: 'a', last_name: 'abc')
expect(user).to_not be_valid
end
it 'is invalid if the last name has less than two characters' do
user = User.create(first_name: 'abc', last_name: 'a')
expect(user).to_not be_valid
end
Your tests should look like this:
it 'invalid length' do
user = User.new(first_name: '#', last_name: '#')
user.valid?
expect(user.errors.count).to eq 2
expect(user.errors[:first_name]).to include "is too short (minimum is 2 characters)"
expect(user.errors[:last_name]).to include "is too short (minimum is 2 characters)"
end
The user.valid? call will run the new user against the validations which will populate the errors.
That's a very verbose test to do a unit test - I highly recommend shoulda matchers. You can test the above in just two lines:
it { is_expected.to ensure_length_of(:first_name).is_at_least(2) }
it { is_expected.to ensure_length_of(:last_name).is_at_least(2) }

Resources