I am trying to test my polymorphic association but I cant seem to get it to pass
Model:
class Foo < ApplicationRecord
belongs_to :bar, polymorphic: true, optional: true
end
Now my test looks like
RSpec.describe Foo, type: :model do
subject { build(:foo) }
it { is_expected.to belong_to(:bar) }
end
The error that I am getting
Foo
is expected to belong to bar required: true (FAILED - 1)
Failures:
Foo is expected to belong to bar required: true
Failure/Error: it { is_expected.to belong_to(:bar) }
Expected Foo to have a belongs_to association called bar (and for the record to fail validation if :bar is unset; i.e., either the
association should have been defined with required: true, or there
should be a presence validation on :bar)
/# ./spec/models/foo_spec.rb:4:in `block (2 levels) in <top (required)>'
Now this association can be a nil value
The issue seems to be:
and for the record to fail validation if :bar is unset;
Since, you have optional: true - this part is not satisfied.
It looks like shoulda assumes a relation should be required, unless you tell it otherwise.
Try modifying this matcher like so
it { is_expected.to belong_to(:bar).optional }
https://github.com/thoughtbot/shoulda-matchers/blob/master/lib/shoulda/matchers/active_record/association_matcher.rb#L304:L321
Related
I have a model in this application that tracks role changes. The test I'm about to describe has been passing for the last couple years no problem and only starting failing as I upgraded the rails version from 5.1 to 5.2.
Here's an excerpt from the model:
class RoleChange < ApplicationRecord
acts_as_paranoid
belongs_to :actor, class_name: 'User', foreign_key: 'actor_id'
belongs_to :subject, class_name: 'User', foreign_key: 'subject_id'
validates :subject_id, presence: true
def subject
User.with_deleted.find(subject_id)
end
...
end
And the assertion that is failing in the spec is the following
require 'rails_helper'
RSpec.describe RoleChange, type: :model do
let(:organization) { create(:organization) }
let(:admin) { organization.users.admins.first }
let!(:user) { create(:employee_user, organization: organization) }
subject { create(:role_change, subject: user, actor: admin) }
describe 'associations' do
it { is_expected.to belong_to(:actor) }
it { is_expected.to belong_to(:subject) }
end
...
end
The second association assertion fails with the following error:
1) RoleChange associations is expected to belong to subject required:
Failure/Error: User.with_deleted.find(subject_id)
ActiveRecord::RecordNotFound:
Couldn't find User without an ID
# ./app/models/role_change.rb:28:in `subject'
This is very frustrating.
When I binding.pry inside of an it block, the subject appears to be valid? and persisted? with a subject_id as you'd expect.
It's only once I run the assertion, the subject_id magically becomes nil.
For additional context, my adventures in the rails console:
when I run the assertion, it fails and says that nil can’t be found:
Totally baffling. Can you help me finish this rails upgrade? It's my last failing test.
UPDATE:
deleting the subject getter method from RoleChange makes the test pass but then I lose the benefit of including deleted users from the query. So... doesn't actually seem like a solution.
I'm new doing unit test in Ruby on Rails, so I need some help in a test.
This is my model User.rb
class User < ApplicationRecord
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
end
I want to create a test to verify this association. I tried doing this on my user_spec.rb
describe 'should validates associations' do
subject { User.new }
it { should belong_to(subject.created_by) }
end
This is the error response
Failures:
1) User should validates associations should belong to
Failure/Error: it { should belong_to(subject.created_by) }
Expected User to have a belongs_to association called (no association > called )
# ./spec/models/user_spec.rb:17:in `block (3 levels) in '
The ActiveRecord shoulda matchers do not require any object of the class to be instantiated for the tests to run. Here, you have initialised a new User instance as the subject, and have tried to pass that to the shoulda matcher to check the belongs_to association.
However, in order to check a belongs_to association on a model with a particular foreign key and class name, the following test can be used:
it { should belong_to(:created_by).with_foreign_key(:created_by_id).class_name('User') }
ActiveRecord matchers take lots of other options apart from the two mentioned above. These options are very well documented in the Shoulda code on GitHub
You give an instance to matcher but it waits for the reference name and the referenced class name. Your test should be like following.
it { should belong_to(:created_by).of_type(User) }
My model has a validation concerning one of it's associated models. My rspec tests of the model are failing due to this validation.
describe NewOfferRange do
it { is_expected.to validate_presence_of(:new_offer) }
it { is_expected.to validate_presence_of(:from) }
it { is_expected.to validate_presence_of(:to) }
end
class NewOfferRange < ApplicationRecord
validates :new_offer, :from, :to, presence: true
validate :unique_for_date_ranges
delegate :hotel, to: :new_offer
def unique_for_date_ranges
if hotel.new_offers.joins(:new_offer_ranges)
.where('new_offer_ranges.from < ? AND new_offer_ranges.to > ?', to, from)
.where.not(new_offer_ranges: { id: id })
.count
.positive?
errors.add(:base, 'Ya hay otra oferta para esas fechas.')
end
end
Those test fail because when trying to run the validation, it says new_offer is nil for the new_offer_range.
I'm also using FactoryGirl for my model factories which has defined a factory for new_offer_range that is valid and has the corresponding references.
Can I somehow tell should matchers to use that factory so as not to get this validation error?
Setting
subject { create :new_offer_range }
before it-blocks should solve the problem. It will set the subject of testing, not a default object, but one that you want to have.
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 }
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