I wanted to see if the validation was working properly, so I wrote a test of the model in Rspec.
models/work_history.rb
class WorkHistory < ApplicationRecord
belongs_to :account
validates :name,
:since_date,
:position, presence: true
validates :is_employed, inclusion: [true, false]
validates :until_date, presence: true, if: -> { :is_employed == false }
end
factories/work_histories.rb
FactoryBot.define do
factory :work_history do
sequence(:name) { |n| "#{n}_company" }
since_date { '2017-04-01' }
until_date { '2021-03-01' }
is_employed { false }
position { 'director' }
department { 'sales' }
association :account
end
end
spec/models/work_history_spec.rb
RSpec.describe WorkHistory, type: :model do
let(:account) { create(:account) }
let(:work_history) { build(:work_history, account: account) }
it "is invalid when is_employed is false and until_date is nil" do
work_history.is_employed = false
work_history.until_date = ''
expect(work_history.valid?).to eq(false)
end
it "is invalid when is_employed is employed" do
work_history.is_employed = 'employed'
expect(work_history.valid?).to eq(false)
end
end
then I run rspec command, but the test did't pass.
this is the rspec test error log
docker-compose run app rspec spec/models
Creating toei-works_app_run ... done
FF
Failures:
1) WorkHistory is invalid when is_employed is false and until_date is nil
Failure/Error: expect(work_history.valid?).to eq(false)
expected: false
got: true
(compared using ==)
Diff:
## -1 +1 ##
-false
+true
# ./spec/models/work_history_spec.rb:10:in `block (2 levels) in <top (required)>'
2) WorkHistory is invalid when is_employed is employed
Failure/Error: expect(work_history.valid?).to eq(false)
expected: false
got: true
(compared using ==)
Diff:
## -1 +1 ##
-false
+true
# ./spec/models/work_history_spec.rb:15:in `block (2 levels) in <top (required)>'
I don't know why Rspec tests fail
Why can't I pass rspec's test?
You're not structuring the if part of your validation correctly. You're currently checking whether the symbol :is_employed? equals false, which it never will. You can check out the correct syntax here:
https://guides.rubyonrails.org/active_record_validations.html#conditional-validation
You can either do
class WorkHistory < ApplicationRecord
belongs_to :account
validates :name,
:since_date,
:position, presence: true
validates :is_employed, inclusion: [true, false]
validates :until_date, presence: true, if: -> { :not_employed? }
private
def not_enployed?
!is_employed?
end
end
or
class WorkHistory < ApplicationRecord
belongs_to :account
validates :name,
:since_date,
:position, presence: true
validates :is_employed, inclusion: [true, false]
validates :until_date, presence: true, unless: Proc.new { |work_history| work_history.is_employed? }
end
Related
I'm running into an error with RSpec and I cannot figure out why it's givin the error. I use FactoryBot to create a sweep with sets a value for fsp_name. This value is somehow not recognized in the validations when trying to validate the first_order.
The error that I'm getting is
Failures:
1) Sweep validates
Failure/Error: validates_presence_of :fsp_from, :fsp_to, :fsp_step, unless: -> (sweep) { sweep.fsp_name.empty? }
NoMethodError:
undefined method `empty?' for nil:NilClass
# ./app/models/sweep.rb:21:in `block in <class:Sweep>'
# ./spec/models/sweep_spec.rb:21:in `block (2 levels) in <top (required)>'
# -e:1:in `<main>'
Finished in 0.8215 seconds (files took 0.37721 seconds to load)
1 example, 1 failure
When I do a debugger before the last test and check #sweep the #sweep.fsp_name is present and return a value like 'Voltage'. I don't understand why the test is failing.
RSpec test: sweep_spec.rb
require 'rails_helper'
include Warden::Test::Helpers
describe Sweep do
def create_sweep
#user = FactoryBot.create(:user)
#group = Group::PRIVATE
#server = FactoryBot.create(:server, user: #user)
#simulation = FactoryBot.create(:simulation, user: #user, group: #group, server: #server)
#sweep = FactoryBot.create(:sweep, simulation: #simulation)
end
it "validates" do
is_expected.to have_many :sweep_points
is_expected.to belong_to :simulation
is_expected.to have_one(:project).through :simulation
is_expected.to belong_to :param_set
create_sweep
is_expected.to validate_numericality_of(:first_disorder).is_greater_than 0
end
end
Factory: sweep.rb
require 'faker'
FactoryBot.define do
factory :sweep do
first_disorder 1
final_disorder 5
fsp_name { Sweep::TESTER_VARIABLES.sample.first }
fsp_from 1
fsp_to 1
fsp_step 1
ssp_name "FLUENCE"
ssp_from 1
ssp_to 1
ssp_step 1
param_set_id 1
state { Faker::Name.last_name }
trait :empty do
end
trait :filled do
param_set { create(:param_set, :filled) }
end
end
end
Model: sweep.rb
Class Sweep < ApplicationRecord
has_many :sweep_points, dependent: :destroy
belongs_to :simulation
has_one :project, through: :simulation
belongs_to :param_set, optional: true
validates_associated :param_set
TESTER_VARIABLES = [['Voltage','VOLTAGE'],['Fluence','FLUENCE'],['Alpha','ALPHA'],['Beta','BETA'],['Fermi level left','FERMI_LEVEL_LEFT'],['Fermi level right','FERMI_LEVEL_RIGHT'],['Seed for RNG','RANDSEED'],['Injection prefactor','INJECTION_PREFACTOR'],['Extraction prefactor','EXTRACTION_PREFACTOR'],['Singlet exciton binding energy','SINGLET_EXCITON_BINDING_ENERGY'],['Triplet exciton binding energy','TRIPLET_EXCITON_BINDING_ENERGY'],['Hole prefactor','HOLE_PREFACTOR'],['Electron prefactor','ELECTRON_PREFACTOR'],['CT-state binding energy','V'],['Minimal number of electrons','MIN_ELECTRONS'],['Minimal number of holes','MIN_HOLES'],['Minimal number of excitons','MIN_EXCITONS']]
VARIABLES = [['Voltage','VOLTAGE']]
validates :first_disorder, numericality: { only_integer: true, greater_than: 0, less_than: 1000 }
validates :final_disorder, numericality: { only_integer: true, greater_than: 0, less_than: 1000 }
validate :first_seq_final
validates_presence_of :fsp_from, :fsp_to, :fsp_step, unless: -> (sweep) { sweep.fsp_name.empty? }
end
I ran into an issue with shoulda-matchers when testing uniqueness of and this thread helped. Rspec validates_uniqueness_of test failing with additional validation errors
I'm guessing that the test is checking the subject, which you haven't set, so it's creating a sweep object on its own. This object wouldn't be generated from factory bot and thus that field would be nil instead of [].
Try adding this inside the describe Sweep:
subject { FactoryBot.create(:sweep, simulation: #simulation) }
I'm using Rails 4 enums and I want to properly test them, so I set these tests up for my enum fields:
it { should validate_inclusion_of(:category).in_array(%w[sale sale_with_tax fees lease tax_free other payroll]) }
it { should validate_inclusion_of(:type).in_array(%w[receivable payable]) }
And this is the model they're validating:
class Invoice < ActiveRecord::Base
belongs_to :user
enum category: [:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll]
enum type: [:receivable, :payable]
validates :user, presence: true
validates :issue_date, presence: true
validates :series, presence: true
validates :folio, presence: true
validates :issuing_location, presence: true
validates :payment_method, presence: true
validates :last_digits, presence: true
validates :credit_note, presence: true
validates :total, presence: true
validates :subtotal, presence: true
validates :category, presence: true
validates_inclusion_of :category, in: Invoice.categories.keys
validates :type, presence: true
validates_inclusion_of :type, in: Invoice.types.keys
end
But when I run the tests I get:
1) Invoice should ensure inclusion of type in [0, 1]
Failure/Error: it { should validate_inclusion_of(:type).in_array([0,1]) }
ArgumentError:
'123456789' is not a valid type
# ./spec/models/invoice_spec.rb:20:in `block (2 levels) in <top (required)>'
2) Invoice should ensure inclusion of category in [0, 1, 2, 3, 4, 5, 6]
Failure/Error: it { should validate_inclusion_of(:category).in_array([0,1,2,3,4,5,6]) }
ArgumentError:
'123456789' is not a valid category
# ./spec/models/invoice_spec.rb:19:in `block (2 levels) in <top (required)>'
I've also tried with string values in the test arrays, but I get the same error and I really don't understand it.
Using Shoulda matchers we can use the following to test the enum
it { should define_enum_for(:type).with([:receivable, :payable]) }
it { should define_enum_for(:category).
with(:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll) }
Try this:
it { should validate_inclusion_of(:category).in_array(%w[sale sale_with_tax fees lease tax_free other payroll].map(&:to_sym)) }
Additionally, for code-cleanup, try putting the valid categories/types in a corresponding constant. Example:
class Invoice < ActiveRecord::Base
INVOICE_CATEGORIES = [:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll]
enum category: INVOICE_CATEGORIES
end
Your migration could be the issue, it should look something like:
t.integer :type, default: 1
You may also consider testing this another way.
Maybe more like:
it "validates the category" do
expect(invoice with category fee).to be_valid
end
Use shoulda matchers along with check for column_type.
it do
should define_enum_for(:type).
with_values(:receivable, :payable).
backed_by_column_of_type(:integer)
end
it do
should define_enum_for(:category).
with_values(:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll).
backed_by_column_of_type(:integer)
end
Just use shoulda matchers:
it { should define_enum_for(:type).with_values([:receivable, :payable]) }
it { should define_enum_for(:category).with_values(:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll)}
You have this string in your validations:
validates_inclusion_of :category, in: Invoice.categories.keys
In case of enum
Invoice.categories.keys #=> ["sale", "sale_with_tax", "fees", "lease", "tax_free", "other", "payroll"]
You should update your object data with one of names of your enum.
I have the following model :
class Court < ActiveRecord::Base
#Relationships
#belongs_to :case, class_name: 'Case', foreign_key: 'case_id'
belongs_to :user, class_name: 'User', foreign_key: 'user_id'
#Scopes
#Attributes
attr_accessible :court_name, :court_notes, :street, :city, :state, :zip
#Validations
validates_lengths_from_database
validates :court_name, presence: true, length: { in: 3..200 }
validates :court_notes, length: { maximum: 250 }
validates :court_notes, :street, :city, :state, :zip, presence: true
validates :street, :city, :state, length: { maximum: 30, message: 'max length allowed is 30' }
validates :zip, numericality: true, length: { is: 5, message: 'length should be 5' }, allow_blank: true
#Callbacks
#Methods
end
And the following spec file :
require 'spec_helper'
describe Court do
context '#object' do
it 'has a valid factory' do
FactoryGirl.build(:court).should be_valid
end
end
context '#associations' do
it { should belong_to(:user) }
end
context '#values' do
it { should respond_to(:court_name) }
it { should respond_to(:court_notes) }
it { should respond_to(:street) }
it { should respond_to(:city) }
it { should respond_to(:state) }
it { should respond_to(:zip) }
end
context '#protected' do
it { should_not allow_mass_assignment_of(:id) }
it { should_not allow_mass_assignment_of(:case_id) }
end
context '#validations' do
it { should validate_presence_of(:court_name) }
it { should ensure_length_of(:court_name).is_at_most(200) }
it { should ensure_length_of(:court_notes).is_at_most(250) }
it { should ensure_length_of(:street).is_at_most(30) }
it { should ensure_length_of(:city).is_at_most(30) }
it { should ensure_length_of(:state).is_at_most(30) }
end
end
When I run the spec, I get the error
1) Court#validations
Failure/Error: it { should ensure_length_of(:city).is_at_most(30) }
Did not expect errors to include "is too long (maximum is 30 characters)" when city is set to "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", got error:
# ./spec/models/court_spec.rb:52:in `block (3 levels) in <top (required)>'
and two other similar errors for city and state. Where is the extra error message being added from? The validation is happening at only one place as far as I know but there are two error messages being produced.
The reason is your custom message:
message: 'max length allowed is 30'
Shoulda expects you have exact error message as default:
'is too long (maximum is 30 characters)'
But you have a different message so expectation fails. You can check Shoula doc to see how to allow custom message.
model
class RecipeIngredient < ActiveRecord::Base
validates :recipe_id, presence: true, on: :save
validates :ingredient_id, presence: true, on: :save
belongs_to :recipe
belongs_to :ingredient
has_many :quantities
end
test
require 'spec_helper'
describe RecipeIngredient do
it { should validate_presence_of(:recipe_id) }
it { should validate_presence_of(:ingredient_id) }
it { should belong_to(:recipe) }
it { should belong_to(:ingredient) }
context "valid" do
subject { RecipeIngredient.new(recipe_id: 1, ingredient_id: 1) }
it { should be_valid }
end
end
returns
Failures:
1) RecipeIngredient
Failure/Error: it { should validate_presence_of(:recipe_id) }
Did not expect errors to include "can't be blank" when recipe_id is set to nil, got error:
# ./spec/models/recipe_ingredient_spec.rb:4:in `block (2 levels) in '
2) RecipeIngredient
Failure/Error: it { should validate_presence_of(:ingredient_id) }
Did not expect errors to include "can't be blank" when ingredient_id is set to nil, got error:
# ./spec/models/recipe_ingredient_spec.rb:5:in `block (2 levels) in '
I don't understand why adding on: :save has broken this test
First off, you don't need to put the on: :save clause. That is the default and can be left off.
This particular test is trying to create a 'status update' for a user.
Here is the full error:
Failure/Error: FactoryGirl.create(:status_update, user: #user, created_at: 1.day.ago)
NoMethodError:
undefined method `*' for nil:NilClass
# ./app/models/status_update.rb:31:in `default_values'
# ./spec/models/user_spec.rb:28:in `block (3 levels) in <top (required)>'
Here is the test:
describe "Status Update Associations" do
before { #user.save }
let!(:older_status_update) do
FactoryGirl.create(:status_update, user: #user, created_at: 1.day.ago)
end
let!(:newer_status_update) do
FactoryGirl.create(:status_update, user: #user, created_at: 1.hour.ago )
end
it "should have status updates in the right order" do
#user.status_update.should == [newer_status_update, older_status_update]
end
end
Since the error is pointing to the status update model I might as well include that here as well. I suspect it's got something to do with some variables being set after initialization and the let! in the test, although I'm stumped with trying different callbacks.
class StatusUpdate < ActiveRecord::Base
belongs_to :user
after_initialize :default_values
attr_accessible :current_weight,
:current_bf_pct,
:current_lbm,
:current_fat_weight,
:change_in_weight,
:change_in_bf_pct,
:change_in_lbm,
:change_in_fat_weight,
:total_weight_change,
:total_bf_pct_change,
:total_lbm_change,
:total_fat_change
validates :user_id, presence: true
validates :current_bf_pct, presence: true,
numericality: true,
length: { minimum: 4, maximum:5 }
validates :current_weight, presence: true,
numericality: true,
length: { minimum: 4, maximum:5 }
validates :current_lbm, presence: true
validates :current_fat_weight, presence: true
def default_values
self.current_fat_weight = self.current_weight * self.current_bf_pct
self.current_lbm = self.current_weight - self.current_fat_weight
end
default_scope order: 'status_update.created_at DESC'
end
Here is the factory that adds the 'current_weight and current_bf_pct to the default_values method.
factory :status_update do
user
current_weight 150
current_bf_pct 0.15
end
Thanks!
It's due to your default_values method.
You're doing self.current_weight * self.current_bf_pct but none of them are set to a numerical value.