I have a Rails 5 setup where RSpec fails to check validations on model subclass. If I manually build the object in console I am able to see the errors which should prevent the record to be valid.
The base model:
class Article < ApplicationRecord
belongs_to :author, class_name: User
validates :author, presence: { message: "L'utente autore dell'articolo è obbligatorio." }
validates :title, presence: { message: "Il titolo dell'articolo è obbligatorio." }
end
The model which inherits from Article:
class LongArticle < Article
mount_uploader :thumbnail, LongArticleThumbnailUploader
validates :excerpt, presence: { message: "L'estratto dell'articolo è obbligatorio." }
validates :thumbnail, presence: { message: "L'immagine di anteprima dell'articolo è obbligatoria." }
end
The factory for these models (FactoryGirl):
FactoryGirl.define do
factory :article do
association :author, factory: :author
title "Giacomo Puccini: Tosca"
factory :long_article do
type "LongArticle"
excerpt "<p>Teatro alla Scala: immenso Franco Corelli.</p>"
thumbnail { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec', 'support', 'images', 'unresized-long-article-thumbnail.jpg')) }
end
end
end
This is the RSpec which doesn't work:
require 'rails_helper'
RSpec.describe LongArticle, type: :model do
describe "is valid with mandatory fields" do
it "should be valid with if all mandatory fields are filled" do
article = FactoryGirl.create(:long_article)
expect(article).to be_valid
end
it "should have an excerpt" do
article = FactoryGirl.create(:long_article)
article.excerpt = nil
expect(article).not_to be_valid
end
it "should have the thumbnail" do
article = FactoryGirl.create(:long_article)
article.thumbnail = nil
expect(article).not_to be_valid
end
end
end
The first spec pass, the other two don't.
I tried to test everything in the console, with the same values, and it works, meaning that the record is invalid as it should be.
Is it possible that with RSpec the validations in the subclass won't work?
I'm sorry for the delay, but I think I have figured out what was breaking my tests.
The problems, actually, were two, and not one as I originally thought.
The first test: should have an excerpt
As suggested by juanitofatas, I have added a byebug line after the one where FactoryGirl builds my model. I have noticed that the model instantiated had class Article and not LongArticle.
I came up noticing that FactoryGirl instantiate a model of the base factory when it first met factory :article do. Then, it adds or overrides the attributes defined into inner factories and treating the type attribute as any one other, ignoring that it drives the STI.
The LongArticle factory should have been defined as a completely different model, at the same level as the Article one is.
The second test: should have the thumbnail
This was a bit silly... I have defined a default_url method in the CarrierWave uploader and, in fact, this is the desired behavior. Test was updated.
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.
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.
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
I have the following factory:
FactoryGirl.define do
factory :poem do
skip_create
title "Poem title"
intro_verse
trait_verse
message_verse
end
end
for the following non active record model class:
class Poem
attr_accessor :title, :intro_verse, :trait_verse, :message_verse
end
Can I create a factory for such a class?
When I run the following test:
it "has a valid factory" do
expect(build(:poem)).to be_valid
end
I get the following error:
Failure/Error: expect(build(:poem)).to be_valid
NoMethodError:
undefined method `valid?'
The error is because the class does not have an instance method valid?. (Active Record models have this defined by default)
You need to come up with some logic for determining whether a Poem instance is valid or not, and write a valid? method accordingly.
IIRC, the syntax expect(something).to be_condition simply calls the method condition? on something and fails if it returns false.
Use the ActiveModel::Validations module to adds the ability to validate class objects like in Active Record:
class Poem
include ActiveModel::Validations
validates :title, presence: true
attr_accessor :title, :intro_verse, :trait_verse, :message_verse
end
poem = Poem.new
poem.valid? #false
poem.title = "title"
poem.valid? #true
I am trying to test the following scenario:
-> I have a model called Team which it just makes sense when it has been created by a User. Therefore, each Team instance has to be related to a User.
In order to test that, I have done the following:
describe Team do
...
it "should be associated with a user" do
no_user_team = Team.new(:user => nil)
no_user_team.should_not be_valid
end
...
end
Which forces me to change the Team model as:
class Team < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :name, :user
validates_presence_of :name
validates_presence_of :user
belongs_to :user
end
Does this seem correct to you? I am just worried of make the :user attribute as accessible (mass assignment).
I usually use this approach:
describe User do
it "should have many teams" do
t = User.reflect_on_association(:teams)
expect(t.macro).to eq(:has_many)
end
end
A better solution would be to use the gem shoulda which will allow you to simply:
describe Team do
it { should belong_to(:user) }
end
it { Idea.reflect_on_association(:person).macro.should eq(:belongs_to) }
it { Idea.reflect_on_association(:company).macro.should eq(:belongs_to) }
it { Idea.reflect_on_association(:votes).macro.should eq(:has_many) }
class MicroProxy < ActiveRecord::Base
has_many :servers
end
describe MicroProxy, type: :model do
it { expect(described_class.reflect_on_association(:servers).macro).to eq(:has_many) }
end
RSpec is a ruby test framework, and not a rails framework.
belongs_to is a rails construct, and not a ruby construct.
Gems like shoulda-matchers connect ruby and rails things and help you write good tests.
Having the above in mind and following official documentation, should help you stay up to date and understand what you are writing.
So, below is what I would write.
User model:
RSpec.describe User, type: :model do
context 'associations' do
it { should have_many(:teams).class_name('Team') }
end
end
Team model:
RSpec.describe Team, type: :model do
context 'associations' do
it { should belong_to(:user).class_name('User') }
end
end