Can't get uniqueness validation test pass with shoulda matchers - ruby-on-rails

I have a shoulda matcher in my achievement_spec.rb and I can't get it pass:
Spec:
- require 'rails_helper'
RSpec.describe Achievement, type: :model do
describe 'validations' do
it { should validate_presence_of(:title) }
it { should validate_uniqueness_of(:title).case_insensitive.scoped_to(:user_id).with_message("you
can't have two achievements with same title")}
it { should validate_presence_of(:user) }
it { should belong_to(:user) }
end
end
Model:
- class Achievement < ActiveRecord::Base belongs_to :user
validates :title, presence: true
validates :user, presence: true
validates :title, uniqueness: {
scope: :user_id,
message: "you can't have two achievements with the same title"
}
enum privacy: [ :public_access, :private_access, :friends_access ]
def description_html
Redcarpet::Markdown.new(Redcarpet::Render::HTML).render(description)
end
end
Error:
- .F..
Failures:
1) Achievement validations should validate that :title is
case-sensitively unique within the scope of :user_id, producing a
custom validation error on failure
Failure/Error: it { should validate_uniqueness_of(:title).scoped_to(:user_id).with_message("you
can't have two achievements with same title") }
Achievement did not properly validate that :title is case-sensitively
unique within the scope of :user_id, producing a custom validation error
on failure.
After taking the given Achievement, setting its :title to ‹"an
arbitrary value"›, and saving it as the existing record, then making a
new Achievement and setting its :title to ‹"an arbitrary value"› as
well and its :user_id to a different value, ‹nil›, the matcher
expected the new Achievement to be invalid and to produce the
validation error "you can't have two achievements with same title" on
:title. The record was indeed invalid, but it produced these
validation errors instead:
* user: ["can't be blank"]
* title: ["you can't have two achievements with the same title"]
# ./spec/models/achievement_spec.rb:29:in `block (3 levels) in <top (required)>'
Finished in 0.16555 seconds (files took 3.13 seconds to load) 4
examples, 1 failure
Failed examples:
rspec ./spec/models/achievement_spec.rb:29 # Achievement validations
should validate that :title is case-sensitively unique within the
scope of :user_id, producing a custom validation error on failure
[Finished in 4.1s with exit code 1] [cmd: ['rspec', '-I
/home/mayur/Downloads/MyWork/ruby/i-rock/spec/models',
'/home/mayur/Downloads/MyWork/ruby/i-rock/spec/models/achievement_spec.rb']]
[dir: /home/mayur/Downloads/MyWork/ruby/i-rock] [path:
/usr/local/rvm/gems/ruby-2.2.4/bin:/usr/local/rvm/gems/ruby-2.2.4#global/bin:/usr/local/rvm/rubies/ruby-2.2.4/bin:/usr/lib64/qt-3.3/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/usr/local/rvm/bin:/home/mayur/.local/bin:/home/mayur/bin]
How can I get rid of above error?
I tried below solution but getting same error:
Change in model:
validates :title, uniqueness: {
case_sensitive: false,
scope: :user_id,
message: "you can't have two achievements with the same title"
}
Change in spec:
it { should validate_uniqueness_of(:title).case_insensitive.scoped_to(:user_id).with_message("you can't have two achievements with same title") }
Error Again:
.F..
Failures:
1) Achievement validations should validate that :title is case-insensitively unique within the scope of :user_id, producing a custom validation error on failure
Failure/Error: it { should validate_uniqueness_of(:title).case_insensitive.scoped_to(:user_id).with_message("you can't have two achievements with same title") }
Achievement did not properly validate that :title is case-insensitively
unique within the scope of :user_id, producing a custom validation error
on failure.
After taking the given Achievement, setting its :title to ‹"an
arbitrary value"›, and saving it as the existing record, then making a
new Achievement and setting its :title to ‹"an arbitrary value"› as
well and its :user_id to a different value, ‹nil›, the matcher
expected the new Achievement to be invalid and to produce the
validation error "you can't have two achievements with same title" on
:title. The record was indeed invalid, but it produced these
validation errors instead:
* user: ["can't be blank"]
* title: ["you can't have two achievements with the same title"]
# ./spec/models/achievement_spec.rb:29:in `block (3 levels) in <top (required)>'
Finished in 0.13566 seconds (files took 3.14 seconds to load)
4 examples, 1 failure
Failed examples:
rspec ./spec/models/achievement_spec.rb:29 # Achievement validations should validate that :title is case-insensitively unique within the scope of :user_id, producing a custom validation error on failure

The failure is occurring due to the error message defined in your spec not matching what's in your model. You're missing the word the in your spec:
Spec
... .with_message("you can't have two achievements with same title")}
Model
... message: "you can't have two achievements with the same title"
Fixing what's defined in your spec in .with_message to match what's defined in your model within the uniqueness validation message on title should resolve the failure.

You need to build a factory and ensure there is a user before running these validations:
describe Achievment do
context 'validations' do
before { FactoryGirl.build(:achievement) }
it do
should validate_uniqueness_of(:title).
scoped_to(:user_id).
case_insensitive
end
end
end
your factory then:
FactoryGirl.define do
factory :achievement
title 'some-string'
user
end
end

Related

Shoulda cannot properly validate case-sensitive unique

Using shoulda and FactoryGirl to test model validation.
Factory
FactoryGirl.define do
factory :tag do
value { Faker::Lorem.word }
user
end
end
Tag Model
class Tag < ApplicationRecord
validates :value,
presence: true,
uniqueness: { case_sensitive: false }
belongs_to :user
has_and_belongs_to_many :cards
end
Tag Spec
RSpec.describe Tag, type: :model do
describe 'validations' do
it { should validate_presence_of(:value) }
it { should validate_uniqueness_of(:value) }
end
describe 'associations' do
it { should belong_to(:user) }
# it { should have_and_belong_to_many(:cards) }
end
end
I get the following error when I run the test,
Failures:
1) Tag validations should validate that :value is case-sensitively unique
Failure/Error: it { should validate_uniqueness_of(:value) }
Tag did not properly validate that :value is case-sensitively unique.
After taking the given Tag, setting its :value to ‹"an arbitrary
value"›, and saving it as the existing record, then making a new Tag
and setting its :value to a different value, ‹"AN ARBITRARY VALUE"›,
the matcher expected the new Tag to be valid, but it was invalid
instead, producing these validation errors:
* value: ["has already been taken"]
* user: ["must exist"]
# ./spec/models/tag_spec.rb:6:in `block (3 levels) in <top (required)>'
# -e:1:in `<main>'
The correct way to test case_insensitive is to using matcher below,
it { should validate_uniqueness_of(:value).case_insensitive }
You can also write with ignoring_case_sensitivity:
it { should validate_uniqueness_of(:value).ignoring_case_sensitivity }

Inclusion in validation and Rspec using FactoryGirl

I'm having troubles testing an ActiveRecord inclusion validation in Rails with Factory Girl and Rspec. The inclusion validation always fails. Here is my code:
class FruitType
has_many :fruits
end
class Fruit
belongs_to :fruit_type
validates :fruit_type_id, numericality: { only_integer: true }
validates :fruit_type_id, inclusion: { in: FruitType.pluck(:id), message: "is invalid" }
end
FactoryGirl.define do
factory :fruit_type_apple, class: FruitType do
name "Apple"
end
end
FactoryGirl.define do
factory :valid_fruit, class: Fruit do
name "Red Apple"
association :fruit_type, factory: :fruit_type_apple
end
end
Rspec test is:
it "should have valid factory" do
f = FactoryGirl.build( :valid_fruit )
puts f.fruit_type_id
puts "\n#{FruitType.all.pluck(:id)}"
expect(f).to be_valid
end
Result is:
1
[1]
F..........
Failures:
1) Fruit when validated should have valid factory
Failure/Error: expect(f).to be_valid
expected # to be valid, but got errors: Fruit type is invalid
As you can see, I've printed out the Fruit Type id list in the test, which includes only 1. And I've printed out the value of fruit_type_id for the fruit, which is 1. Yet, the inclusion validation still fails.
If I do the same thing in the rails console just by creating fruits and types manually, the validation works fine, it's just when I run the test I'm seeing this behavior. Any ideas? I must be missing something about Factory Girl here.
You shouldn't validate the fruit_type_id attribute, rather use presence validation
validates :fruit_type, presence: true

Why doesn't my rspec test pass when my model validation fails as it should

My model has validation like:
# Account Model
validates :email, presence: true
validates :password, presence: true
So I created a factory_girl for this model that has blank values like:
factory :account do
end
trait :empty do
email ""
password ""
end
So my test looks like:
it "doesn't allow empty fields" do
account = create(:account, :empty)
account.should_not be_valid
end
So I am saying that this model, with empty fields, should fail the validation with "should_not".
Yet when I run rspec I get this error:
Validation failed: Email can't be blank, Password can't be blank
# ./spec/models/account_spec.rb:29:in `block (3 levels) in <top (required)>'
What is the problem with my test?
The rspec error comes from this line account = create(:account, :empty), not this one account.should_not be_valid
create will attempt to create the record in the database, and go through the validation. use build instead

validate_presence_of not working as expected with incremental counter

Consider the following code:
campaign_spec.rb:
describe Campaign do
before :each do
#campaign = Campaign.new
end
it { should validate_presence_of :position }
end
campaign.rb:
class Campaign < ActiveRecord::Base
attr_accessible :position
validates :position, presence: true
default_scope order(:position)
before_validation :next_position
# sets the position to the next id (1 if none exist) before validation
def next_position
if self.position.blank?
self.position = Campaign.select("coalesce(max(position),0) + 1 as position").reorder(nil).first.position
end
end
end
spec output:
Failures:
1) Campaign should require position to be set
Failure/Error: it { should validate_presence_of :position }
Expected errors to include "can't be blank" when position is set to nil, got errors: ["name can't be blank (nil)"]
# ./spec/models/campaign_spec.rb:9:in `block (2 levels) in <top (required)>'
From what my model is saying, I should be able to create a Campaign without giving it a position. It sets the position if none exists before validation. So why is my spec not passing? I'm thinking that maybe it's not calling my before_validation method?
should validate_presence_of sets the value of the attribute to nil and then runs the validations. Since it is nil, nil.blank? returns true, so your callback is executed, setting it back to some value, and hence validation is found not to work.
Fact is that you don't need to validate this field if you are sure it is never empty. Instead, write the test to check whether it is indeed automatically populated when set to nil.
it 'populates position if blank before validations' do
subject.position = nil
subject.valid?
subject.position.should_not be_nil
end

rSpec Testing Rails using Shoulda matchers

I have been starting to learn testing in my Rails app and am using rSpec and Shoulda.
I have the following test which works:
it { should respond_to(:park_name) }
However, what I don't understand is, what is this being run on? Is this being run on the Model itself or an instance of the model and if it's an instance of the Model then is it automatically using my Factory Girl factory?
Any simple explanations on what is actually occurring here?
UPDATE:
Ok, So I have this:
describe 'validations' do
subject { FactoryGirl.build(:coaster) }
it { should validate_presence_of(:name) }
it { should validate_presence_of(:speed) }
it { should validate_presence_of(:height) }
end
But the tests are failing. Any ideas?
Coaster.rb:
class Coaster < ActiveRecord::Base
extend FriendlyId
friendly_id :slug, use: :slugged
belongs_to :park
belongs_to :manufacturer
attr_accessible :name,
:height,
:speed,
:length,
:inversions,
:material,
:lat,
:lng,
:park_id,
:notes,
:manufacturer_id,
:style,
:covering,
:ride_style,
:model,
:layout,
:dates_ridden,
:times_ridden,
:order,
:on_ride_photo
scope :by_name_asc, lambda {
order("name ASC")
}
scope :made_from, lambda { |material|
where("material = ?", material)
}
scope :wooden, lambda {
made_from "wood"
}
scope :steel, lambda {
made_from "steel"
}
delegate :name, :location_1, :location_2, :location_3, :location_4,
to: :park,
allow_nil: true,
prefix: true
delegate :name, :url,
to: :manufacturer,
prefix: true
validates :name,
:presence => true
validates :height,
allow_nil: true,
numericality: {greater_than: 0}
validates :speed,
allow_nil: true,
numericality: {greater_than: 0}
validates :length,
allow_nil: true,
numericality: {greater_than: 0}
Test Results:
1) Coaster validations should require speed to be set
Failure/Error: it { should validate_presence_of(:speed) }
Expected errors to include "can't be blank" when speed is set to nil, got no errors
# ./spec/models/coaster_spec.rb:75:in `block (3 levels) in '
2) Coaster validations should require height to be set
Failure/Error: it { should validate_presence_of(:height) }
Expected errors to include "can't be blank" when height is set to nil, got no errors
# ./spec/models/coaster_spec.rb:76:in `block (3 levels) in '
SIMILAR QUESTION:
I have this test:
describe 'methods' do
subject { FactoryGirl.build(:coaster) }
it "should return a formatted string of coaster name at park name" do
name_and_park.should eq('Nemesis at Alton Towers')
end
end
Coaster.rb:
def name_and_park
[name, park.name].join (' at ')
end
Error when running the test:
2) Coaster methods should return a formatted string of coaster name at
park name
Failure/Error: name_and_park.should eq('Nemesis at Alton Towers')
NameError:
undefined local variable or method name_and_park' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_6:0x007f84f4161798>
# ./spec/models/coaster_spec.rb:111:inblock (3 levels) in '
It says name_and_park cannot be called but surely that method should be being called on the instance of Coaster that is being made in the subject line? No?
It's being run on "subject", which is either explicitly defined through the subject method or implicitly defined by passing in a class as the argument to describe, in which case an instance of that class is instantiated and made the subject of the test. See https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/explicit-subject and https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/implicit-subject
As for the answer to the Update question, in your model validations for :speed and :length, you have allow_nil: true, which is why those two tests are failing. Part of the definition of validates_presence_of is that nil is not a settable value.
As for your latest question, I think you may be confused about the use of implicit subjects. If should is used by itself, it will indeed default to whatever the subject is, but if you include a subject yourself, as you have in this case with name_and_park, it won't treat that as a method of the default subject, it must have a definition within the current namespace. In your case, you would need to say subject.name_and_park.should ....
On a related aside, StackOverflow is best used when you ask a specific question or related set of questions and get an answer. For a variety of reasons, it's not intended for ongoing debugging sessions. One of those reasons is that it becomes tedious tracking substantial, sequential updates of the original question and answer.

Resources