How to properly use shoulda matchers with uniqueness validation? - ruby-on-rails

I am trying rspec and shoulda matchers as my new testing framework and I'm a little confused as to how to get the should validate_uniqueness_of(:attribute) test to pass. Here is my simple model:
class Employee < ActiveRecord::Base
validates :name, presence: true
validates :title, presence: true
validates :department, presence: true
validates :avatar, presence: true
validates :email, presence: true, uniqueness: true
validates :reports_to, presence: true
mount_uploader :avatar, EmployeeAvatarUploader, on: :file_name
end
And here is the initial test:
RSpec.describe Employee, type: :model do
it { should validate_presence_of(:name) }
it { should validate_presence_of(:title) }
it { should validate_presence_of(:department) }
it { should validate_presence_of(:avatar) }
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should validate_presence_of(:reports_to) }
end
This fails with a description of the error pasted below:
`1) Employee should validate uniqueness of email
Failure/Error: should validate_uniqueness_of(:email)
Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
validate_uniqueness_of works by matching a new record against an
existing record. If there is no existing record, it will create one
using the record you provide.
While doing this, the following error was raised:
PG::NotNullViolation: ERROR: null value in column "title" violates not-null constraint
DETAIL: Failing row contains (21, null, null, null, null, null, null, 2017-06-27 01:07:49.125445, 2017-06-27 01:07:49.125445, null, null).
: INSERT INTO "employees" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"
The best way to fix this is to provide the matcher with a record where
any required attributes are filled in with valid values beforehand.`
So my impression from the documentation was that my test should work as is. From the shoulda matcher documentation, I've tried this version of my test:
RSpec.describe Employee, type: :model do
it { should validate_presence_of(:name) }
it { should validate_presence_of(:title) }
it { should validate_presence_of(:department) }
it { should validate_presence_of(:avatar) }
it { should validate_presence_of(:email) }
it 'should validate uniqueness of email' do
email1 = FactoryGirl.create(:account, email: 'email#blackducksoftware.com')
email2 = FactoryGirl.build(:account, email: 'email#blackducksoftware.com' )
should validate_uniqueness_of(:email)
end
it { should validate_presence_of(:reports_to) }
end
This didn't seem to work either. Then I tried a variation of this test found by clicking Rspec validates_uniqueness_ofthis link
But still no luck. Could someone please direct me to the proper way of making this validation work?

You should be able to call it this way:
describe "validations" do
subject { Employee.new(title: "Here is the content") }
it { should validate_uniqueness_of(:email) }
end
Or with FactoryGirl:
describe "validations" do
subject { FactoryGirl.build(:employee) }
it { should validate_uniqueness_of(:email) }
end
Your employees table has a database level constraint requiring title, so you need to create a valid employee object first.
It looks as though in your second example you're building a account instead of employee.
See docs here: docs

describe Employee, type: :model do
context 'validations' do
it { should validate_uniqueness_of(:email) }
end
end
Here, subject is implicit. A new instance of Employee is built(not created) and validation is verified.

Related

Ruby on Rails: RSpec not working with validation

I have fairly simple model but I'm getting RSpec failures that I cannot find the solution to.
Model -
store_accessor :properties, :fields
store_accessor :priorities
belongs_to :user
belongs_to :external_service
validates :user, :external_service, presence: true
validate :service_enabled?
validates_presence_of :properties, message => "This field is non editable"
Rspec -
require 'rails_helper'
module Application
describe Integration, type: :model do
it { should validate_presence_of(:user) }
it { should validate_presence_of(:external_service) }
it { should validate_presence_of(:properties) }
context 'external service' do
let(:service) { Application::ExternalService.new }
before do
allow(subject).to receive(:external_service).and_return(service)
end
end
end
end
This is the failure that I am getting:
Failure -
Application::Integration should require properties to be set
Failure/Error: it { should validate_presence_of(:properties) }
Expected errors to include "can't be blank" when properties is set to nil,
got errors:
* "can't be blank" (attribute: user, value: nil)
* "can't be blank" (attribute: external_service, value: nil)
* "The service must be enabled to add an integration." (attribute: external_service, value: nil)
* "This field is non editable" (attribute: properties, value: nil)
By default validates_presence_of consider error message as can't be blank. but, as you are setting custom validation message then, you have to verify the same using with_message in spec
it { should validate_presence_of(:properties).with_message('This field is non editable') }
Example for validate_presence_of with with_message

Rails - FactoryGirl - test models [validation]

I would like to test my models but all informations that I could find seems to be outdated. My goal is to test each individual validation.
My model:
class Author < ActiveRecord::Base
has_and_belongs_to_many :books
before_save :capitalize_names
validates :name, :surname, presence: true, length: { minimum: 3 },
format: { with: /[a-zA-Z]/ }
private
def capitalize_names
self.name.capitalize!
self.surname.capitalize!
end
end
and my factorygirl define:
FactoryGirl.define do
factory :author do |f|
f.name { Faker::Name.first_name }
f.surname { Faker::Name.last_name }
end
end
So now, I want to test whether name is not shorter than 3 characters.
My context:
context 'when first name is too short' do
it { expect( FactoryGirl.build(:author, name: 'Le')).to
be_falsey }
end
I know it's invalid because of [FactoryGirl.build(:author, name: 'Le')] returns hash instead of boolean value. So now, how should I test it? What matcher should I use?
[SOLVED]
Use be_valid instead of be_falsey. Now it should look like :
context 'when first name is too short' do
it { expect( FactoryGirl.build(:author, name: 'Le')).not_to
be_valid }
end

Rspec test help - email uniqueness test failing yet it works in my app

I have been creating an application based on the Hartl course, and have added in the concept of Organization, which has_many users. The tests are all the standard tests Hartl recommends up to section 9.2 of the guide book. Since implementing Organizations into the application, one of the test cases is failing "when email address is already taken" - this should block a user from signing up with the same email address twice. What is odd is the fact that this is working in the application itself (form error - "users email address is already taken" thrown) but not in my tests. Can you help and indicate why this has broken please?
User code:
class User < ActiveRecord::Base
belongs_to :organization
#accepts_nested_attributes_for :organization
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(?:\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
validates :organization, presence: true
Organization code:
class Organization < ActiveRecord::Base
validates :organization_name, presence: true, length: { maximum: 50 }, uniqueness: true
has_many :users, :inverse_of => :organization
accepts_nested_attributes_for :users
User spec:
require 'spec_helper'
describe User do
before do
#user = FactoryGirl.create(:user)
end
subject { #user }
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should respond_to(:remember_token) }
it { should respond_to(:authenticate) }
it { should be_valid }
...
describe "when email address is already taken" do
before do
user_with_same_email = #user.dup
user_with_same_email.email = #user.email.upcase
user_with_same_email.save
end
it { should_not be_valid }
end
Factory Girl Code:
FactoryGirl.define do
factory :organization do
organization_name "Example Org"
trait :wrong do
organization_name "Wrong Org"
end
trait :also_wrong do
organization_name "Another Wrong Org"
end
end
factory :user do
association :organization
name "Example Name"
email "email#example.com"
password "foobar"
password_confirmation "foobar"
trait :wrong_org do
association :organization, :factory => [:organization, :wrong]
end
trait :wrong_org2 do
association :organization, :factory => [:organization, :also_wrong]
end
end
end
The error thrown from the Rails console is as follows:
1) User when email address is already taken should not be valid
Failure/Error: it { should_not be_valid }
expected #<User id: 5287, name: "Example Name", email: "email#example.com", created_at: "2014-07-22 15:04:33", updated_at: "2014-07-22 15:04:33", password_digest: "$2a$04$jrxyuz9e574BoaAhZm6xkOUeAY5spyDut2CCEvAykMu...", organization_id: 5025, remember_token: "339dfafcac7bc5925dbf4e44f60a782f3bbbaa1b">.valid? to return false, got true
I've tried changing the code inside the test, but no matter what I do it keeps throwing an error. As mentioned above, when I open up the application in my local server I can use all functions, and when I try to sign up using a duplicate email address it won't let me. What's wrong with my test code?
The #user is completely valid:
You create your subject, #user. This is valid
You create the user_with_same_email
That user is not valid, because it has the same email as #user
Saving user_with_same_email returns false, but that is not checked in your test
The duplicate user is not saved in the db
The original user is still valid
A correct test would just #dupthe user (or make a new one with the same email), and then check that the new record is not valid.

I just can't get my valid password test to pass

I'm trying to get my valid password test to pass. When I run it it seems that the password_digest hash is different. I don't know what to do to get them to match.
I was mostly using the book "Ruby on Rails Tutorial: Learn Rails by Example" by Michael Hartl and it seems that he gets this to pass.
Additionally, my application code works just as expected. I can create a user and authenticate them in the console so this is only breaking in tests.
I'm fairly new to testing so I may be missing something obvious here.
Thanks for your help!
I am using bcrypt with has_secure_password and here's part relavent user spec code:
describe User do
before { #user = User.new(name: "Example User", email: "user#example.com", password: "foobar", password_confirmation: "foobar") }
subject { #user }
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should respond_to(:authenticate) }
it { should be_valid }
describe "return value of authenticate method" do
before { #user.save }
let(:found_user) { User.find_by(email: #user.email) }
describe "with a valid password" do
it { should eq found_user.authenticate(#user.password) }
end
describe "with an invalid password" do
let(:user_for_invalid_password) { found_user.authenticate('invalid') }
it { should_not eq user_for_invalid_password }
specify { expect(user_for_invalid_password).to be_false }
end
end
end
And this is my user model
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: VALID_EMAIL_REGEX }
has_secure_password
validates :password_confirmation, presence: true
validates :password, length: { minimum: 6 }
end
The problem is in your before { #user.save }. You may not have realized it, but before defaults to before(:each), which means this code runs before each example in the group. You probably expected it to default to before(:all), which runs only once, before all examples in the group.
As you mentioned, you aren't clearing the database after each test. Therefore, before each test, you are calling #user.save. This silently fails validation by returning false, because you are trying to create another user with the same email address. It's better to use #save! so validation exceptions are thrown, making problems like this more obvious.
To summarize:
Explicitly use before(:all) or before(:each)
Use #save! instead of #save
Clear the database between each test (try out Database Cleaner)

Rspec test for validity of new object not passing

I worked through all of Michael Hartl's Ruby on Rails Tutorial with all the tests passing. Now that I'm going back and making changes to the site to suit my own needs, it's not as cut and dry as "the tests in this section aren't passing." I've created a new "Charity" object that is strongly based on Hartl's "Micropost" object. The only difference is that instead of having "content" the object has a :name, :description and :summary.
This is the code for the test that is failing, (specifically "it { should be_valid }") which is located in /charity_spec.rb:
require 'spec_helper'
describe Charity do
let(:user) { FactoryGirl.create(:user) }
before { #charity = user.charities.build(summary: "Lorem ipsum") }
subject { #charity }
it { should respond_to(:name) }
it { should respond_to(:user_id) }
it { should respond_to(:summary) }
it { should respond_to(:description) }
it { should respond_to(:user) }
its(:user) { should == user }
it { should be_valid }
...
The test actually passes at first, but once I add the validations to the charity.rb file, they return;
Failures:
1) Charity
Failure/Error: it { should be_valid }
expected valid? to return, true, got false
...
Here's the charity.rb:
class Charity < ActiveRecord::Base
attr_accessible :name, :description, :summary
belongs_to :user
validates :name, presence: true, length: { maximum: 40 }
validates :summary, presence: true
validates :description, presence: true
validates :user_id, presence: true
default_scope order: 'charities.created_at DESC'
end
I'm sure it's something stupid, but my understanding of everything is so weak I can't figure out what I'm doing wrong, my feeling is that it's something wrong with my factory, but I really don't know.
Here's my charity factory located in the factories.rb:
factory :charity do
name "Lorem ipsum"
summary "Lorem ipsum"
description "Lorem ipsum"
user
end
When I remove the :name, :summary, and :description validations from charity.rb, the test passes. For good measure, here's the beginning of my user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
has_many :charities
has_many :microposts, dependent: :destroy
Use your factory to have a proper charity:
before { #charity = user.charities.build(FactoryGirl.attributes_for(:charity)) }
It failed because you validate presence of attributes which were not set like name
If you need more background on FactoryGirl, their documentation is really good.

Resources