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
Related
I am testing model using rspec and factory Girl
My model
class Currency < ActiveRecord::Base
has_many :countries
validates :name, presence: true
validates :name, uniqueness: true
before_destroy :safe_to_delete
def safe_to_delete
countries.any? ? false : true
end
end
My factory girl
FactoryGirl.define do
factory :currency, class: 'Currency' do
sequence(:name) { |i| "Currency-#{i}" }
end
end
My currency_spec.rb is
require 'rails_helper'
describe Currency , type: :model do
let(:currency) { create(:currency) }
let(:currency1) { create(:currency) }
let(:country) { create(:country) }
describe 'associations' do
subject {currency}
it { should have_many(:countries) }
end
describe 'validations' do
subject {currency}
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name) }
end
describe 'method save_to_delete' do
context 'case false' do
before { country.update_column(:currency_id, currency.id) }
subject { currency.destroy }
it { is_expected.to be_falsy }
end
context 'case true' do
before { country.update_column(:currency_id, currency1.id) }
subject { currency.destroy }
it { is_expected.to be_truthy }
end
end
end
The error is:
Failure/Error: let(:currency) { create(:currency) }
ActiveRecord::RecordInvalid:
A validação falhou: Name não está disponível
Even though I disable the presence and uniqueness validations in the model, the problem continues
Who can help me
Did you properly create the migration to include the name on currencies at database level?
Because I created the migration here locally and the tests passed.
Please take a look on the below code.
It is what I did locally and is working here!
1. Migration
file: db/migrate/2021XXXXXXXXXX_create_currencies.rb
class CreateCurrencies < ActiveRecord::Migration
def change
create_table :currencies do |t|
t.string :name
end
end
end
2. Model
app/models/currency.rb
class Currency < ActiveRecord::Base
has_many :countries
validates :name, presence: true, uniqueness: true # Can be oneliner ;)
before_destroy :safe_to_delete
def safe_to_delete
countries.empty? # Much simpler, right? ;)
end
end
3. Factory
spec/factories/currency.rb
FactoryGirl.define do
factory :currency do
sequence(:name) { |i| "Currency-#{i}" }
end
end
4. Tests
spec/models/currency_spec.rb
require 'rails_helper'
describe Currency, type: :model do
let(:currency1) { create(:currency) }
let(:currency2) { create(:currency) }
let(:country) { create(:country) }
describe 'associations' do
subject { currency1 }
it { should have_many(:countries) }
end
describe 'validations' do
subject { currency1 }
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name) }
end
describe 'when the currency is being deleted' do
context 'with countries associated' do
before { country.update_column(:currency_id, currency1.id) }
subject { currency1.destroy }
it { is_expected.to be_falsy }
end
context 'with no countries associated' do
before { country.update_column(:currency_id, currency2.id) }
subject { currency1.destroy }
it { is_expected.to be_truthy }
end
end
end
Test Execution
Finally, the tests should work correctly with the above setup!
spec/models/currency_spec.rb
rspec spec/models/currency_spec.rb
D, [2021-03-06T03:31:03.446070 #4877] DEBUG -- : using default configuration
D, [2021-03-06T03:31:03.449482 #4877] DEBUG -- : Coverband: Starting background reporting
.....
Top 5 slowest examples (0.10688 seconds, 11.4% of total time):
Currency when the currency is being deleted with countries associated should be falsy
0.04095 seconds ./spec/models/currency_spec.rb:23
Currency associations should have many countries
0.03529 seconds ./spec/models/currency_spec.rb:10
Currency when the currency is being deleted with no countries associated should be truthy
0.01454 seconds ./spec/models/currency_spec.rb:29
Currency validations should validate that :name cannot be empty/falsy
0.00812 seconds ./spec/models/currency_spec.rb:15
Currency validations should validate that :name is case-sensitively unique
0.00797 seconds ./spec/models/currency_spec.rb:16
Finished in 0.93948 seconds (files took 8.04 seconds to load)
5 examples, 0 failures
All tests passed ✅
I am writing some rspec tests on my app and there is a bug, it show me that error :
undefined method ` =' for #<Agency id: nil, name: nil, ip_adress: nil>
There is my tests :
require 'rails_helper'
RSpec.describe Agency, type: :model do
it "should create the agency if all fields are filled" do
expect(FactoryGirl.build(:agency)).to be_valid
end
it "should fail if name is missing" do
expect(FactoryGirl.build(:agency, name: nil)).to_not be_valid
end
it "should fail if ip_adress is missing" do
expect(FactoryGirl.build(:agency, ip_adress: nil)).to_not be_valid
end
it "should fail if there is a double name in db" do
agency = FactoryGirl.create(:agency)
expect(FactoryGirl.build(:agency, name: agency.name)).to_not be_valid
end
end
My agency model :
class Agency < ActiveRecord::Base
module Agencymod
attr_accessor :name, :ip_adress
end
has_many :users
has_many :incidents
has_many :field_agency_agencies, dependent: :destroy
has_many :field_agencies, through: :field_agency_agencies
# # Regexp for the postal code.
# cp_regexp = /\A((0[1-9])|([1-8][0-9])|(9[0-8])|(2A)|(2B))[0-9]{3}\z/
# # Regexp for email.
# email_regexp = /\A[a-zA-Z0-9._-]+#[a-z0-9._-]{2,}\.[a-z]{2,4}$\z/
# # Regexp for phone number.
# phone_regexp = /\A(0|\+33|0033)[1-9][0-9]{8}\z/
# # Regexp for ip address.
ip_regexp = /\A(?:(?:[1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\z/
validates :name, presence: true,
uniqueness: { case_sensitive: false }, length: { in: 0..44 }
validates :ip_adress, presence: true, format: { with: ip_regexp }, length: { in: 0..49 }
end
And finally my factory :
factory :agency, class: Agency do |f|
f.name { Faker::Address.city }
f.ip_adress "8.8.8.8"
end
It is the first time this error appear and when I have tested the user model it works very well...
Sorry for my poor english :)
Thanks to everyone post answer.
I've found the error it appear there are an space between the f.name and the { Faker::Address.city }
When I have removed it, it show me the same error but with undefined method'name='... so I writed the factory like that :
f.name Faker::Address.city
and it works very well ...
My others factories are writted as below :
factory :user do |f|
f.surname { Faker::Name.first_name }
f.name { Faker::Name.last_name }
f.pseudo { Faker::Internet.user_name }
f.password "password"
f.email { Faker::Internet.free_email }
f.type_user_id 23
f.agency_id 2
f.tel "0606060606"
f.ip_addr { Faker::Internet.ip_v4_address }
end
and are working well too ..
f.name "TEST" # That works !
f.name"TEST" # Works too
f.name Faker::Address.city # Works
f.name { Faker::Address.city } # Nope
Issue in gem ?
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.
My associations aren't so complex but I've hit a wall making them work with FactoryGirl:
Text: blast_id:integer recipient_id:integer
class Text < ActiveRecord::Base
belongs_to :blast
belongs_to :recipient, class_name: "User"
validates :blast_id, presence: true
validates :recipient_id, presence: true
end
Blast: content:string author_id:integer
class Blast < ActiveRecord::Base
belongs_to :author, class_name: "User"
has_many :texts
validates :author_id, presence: true
end
User: name:string, etc. etc.
class User < ActiveRecord::Base
has_many :blasts, foreign_key: "author_id"
validates :name, presence: true
end
In FactoryGirl I've got:
FactoryGirl.define do
factory :user, aliases: [:author, :recipient] do |u|
sequence(:name) { Faker::Name.first_name }
end
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: nil, recipient: FactoryGirl.create(:user) )
end
end
factory :text do
blast
association :recipient, factory: :user
end
end
Finally, some specs which all fail because Texts is not valid
require 'spec_helper'
describe Text do
User.destroy_all
Blast.destroy_all
Text.destroy_all
let!(:user) { FactoryGirl.create(:user) }
let!(:blast) { FactoryGirl.create(:blast, author: user) }
let(:text) { blast.texts.first }
subject { text }
it { should be_valid }
describe "attributes" do
it { should respond_to(:blast) }
it { should respond_to(:recipient) }
its(:blast) { should == blast }
its(:recipient) { should == recipient }
end
describe "when blast_id is not present" do
before { text.blast_id = nil }
it { should_not be_valid }
end
describe "when recipient_id is not present" do
before { text.recipient_id = nil }
it { should_not be_valid }
end
end
All the specs fail on FactoryGirl blast creation with:
1) Text
Failure/Error: let!(:blast) { FactoryGirl.create(:blast, author: user) }
ActiveRecord::RecordInvalid:
Validation failed: Texts is invalid
# ./spec/models/text_spec.rb:8:in `block (2 levels) in <top (required)>'
I've tried various iterations of the association code in the FactoryGirl docs and other question answers like this one but my situation is different enough that I can't get it to work.
If you've made it this far, thank you! Super grateful for any leads.
Your factory for "blast" should look like
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: blast, recipient: FactoryGirl.create(:user) )
end
end
In other words, you immediately create the correct "parent" by connecting the newly created blast to the newly created tekst
To further dry your code, have a look at https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#configure-your-test-suite, describing how to get rid of using "FactoryGirl." over and over again by setting
config.include FactoryGirl::Syntax::Methods
once in your settings
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.