shoulda-matchers presence true on save - ruby-on-rails

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.

Related

Model's validation test does not work in rspec

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

How to fix "NoMethodError: undefined method `cf_type' for nil:NilClass" for factorybot generated association

I get the following rspec errors because the association CustomField is nil, but it should be generated by Factorybot and I cannot figure out why it is not. The project association works just fine.
Rspec Tests:
it { should belong_to(:custom_field) }
it { should validate_presence_of(:custom_field) }
Rspec Errors:
NoMethodError: undefined method `cf_type' for nil:NilClass
0) CustomFieldValue validations should validate that :custom_field_id cannot be empty/falsy
Failure/Error: ["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)
NoMethodError:
undefined method `cf_type' for nil:NilClass
# ./app/models/custom_field_value.rb:13:in `option_field?'
# ./spec/models/custom_field_value_spec.rb:18:in `block (3 levels) in <top (required)>'
NoMethodError: undefined method `cf_type' for nil:NilClass
0) CustomFieldValue validations should belong to custom_field required: true
Failure/Error: ["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)
NoMethodError:
undefined method `cf_type' for nil:NilClass
# ./app/models/custom_field_value.rb:13:in `option_field?'
# ./spec/models/custom_field_value_spec.rb:20:in `block (3 levels) in <top (required)>'
Model: CustomFieldValue
class CustomFieldValue < ApplicationRecord
belongs_to :custom_field
belongs_to :project
validates :custom_field, :project, presence: :true
validate :options_existence, if: :option_field?
validate :allowed_value_for_option_fields, if: -> cfv { cfv.option_field? && cfv.options_exist? }
# Conditions for validations
def option_field?
["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)
end
def options_exist?
!custom_field.custom_field_options.empty?
end
private
# Custom validations
# Validate that option fields have at least 1 option defined
def options_existence
if custom_field.custom_field_options.empty?
errors.add(:base, "No custom field option was found")
end
end
# Validation that only custom field option id can be stored for custom fields with options defined
def allowed_value_for_option_fields
if !custom_field.custom_field_options.map{|cfo| cfo.id.to_s}.include?(string_value)
errors.add(:base, "Custom field option id is the only accepted value")
end
end
Model: CustomField
class CustomField < ApplicationRecord
enum cf_type: { string: 10, text: 20, number: 30, single_select: 40,
multi_select: 50, checkbox: 60, radiobutton: 70, date: 80,
single_user: 90, multi_user: 100, label: 110 }
belongs_to :organization
has_many :custom_field_values, dependent: :destroy
has_many :custom_field_options, dependent: :destroy
accepts_nested_attributes_for :custom_field_options
validates :cf_type, :name, :organization, presence: :true
end
Model: Project
class Project < ApplicationRecord
belongs_to :organization
has_many :custom_field_values, dependent: :destroy
validates :name, :key, :organization, presence: :true
validates :key, uniqueness: { case_sensitive: false }, length: { minimum: 2, maximum: 25 }
# Validation of format
# Valid Input: abcd, xyz, aaa, aa-bb
# Invalid Input: abC, XYZ, 123, ABC123, -abc, abc-
validates_format_of :key, :with => /\A(?!-)[a-z][-\w]+(?<!-)\z/
accepts_nested_attributes_for :custom_field_values
end
RSpec: custom_field_value_spec.rb
require "rails_helper"
RSpec.describe CustomFieldValue, :type => :model do
subject {
create(:custom_field_value)
}
context "validations" do
it { should validate_presence_of :project }
it { should validate_presence_of :custom_field }
it { should belong_to(:project) }
it { should belong_to(:custom_field) }
it { expect { create(:custom_field_value, number_value: 1) }.to raise_error(ActiveRecord::RecordInvalid, /Value can be set only for one of the string_value, text_value, number_value or date_value/) }
it { expect { create(:custom_field_value_for_checkbox) }.to raise_error(ActiveRecord::RecordInvalid, /Custom field option id is the only accepted value/) }
end
end
Factory: custom_field_values.rb (factorybot)
FactoryBot.define do
factory :custom_field_value do
sequence(:string_value) { |n| "My String#{n}" }
association :project
association :custom_field, cf_type: :string
end
factory :custom_field_value_for_checkbox, class: CustomFieldValue do
sequence(:string_value) { |n| "My String#{n}" }
association :project
association :custom_field, factory: :custom_field_with_custom_field_options, cf_type: :checkbox
end
end
Factory: custom_fields.rb (factorybot)
FactoryBot.define do
factory :custom_field do
cf_type { :string }
sequence(:name) { |n| "Name#{n}" }
sequence(:description) { |n| "Description#{n}" }
association :organization
factory :custom_field_with_custom_field_values do
transient do
custom_field_values_count { 3 }
end
after(:create) do |custom_field, evaluator|
create_list(:custom_field_value, evaluator.custom_field_values_count, custom_field: custom_field)
end
end
factory :custom_field_with_custom_field_options do
transient do
custom_field_options_count { 3 }
end
after(:create) do |custom_field, evaluator|
create_list(:custom_field_option, evaluator.custom_field_options_count, custom_field: custom_field)
end
end
factory :custom_field_with_custom_field_values_and_options do
transient do
custom_field_values_and_options_count { 3 }
end
after(:create) do |custom_field, evaluator|
create_list(:custom_field_value, evaluator.custom_field_values_and_options_count, custom_field: custom_field)
create_list(:custom_field_option, evaluator.custom_field_values_and_options_count, custom_field: custom_field)
end
end
end
end
Factory: projects.rb (factorybot)
FactoryBot.define do
factory :project do
sequence(:name) { |n| "Name#{n}" }
sequence(:key) { |n| random_name }
association :organization
end
end
def random_name(length=5)
source = ('a'..'z').to_a.shuffle.join
name = ""
length.times{ name += source[rand(source.size)].to_s }
return name
end
You can stub your conditional validation to make your test pass:
RSpec.describe CustomFieldValue, :type => :model do
subject {
create(:custom_field_value)
}
context "validations" do
before { allow(subject).to receive(:option_field?).and_return(false) }
it { should validate_presence_of :custom_field }
end
end

RSpec model validation test error in Proc

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) }

Rails 4 - error creating associations through Fabricator in Rspec tests

Ok, i am trying to use Fabricator with my Rspec tests to mock some data for the tests. I'm having some trouble with a belongs_to association, however. Here's what i have so far:
user.rb
class User < ActiveRecord::Base
authenticates_with_sorcery!
belongs_to :organization
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates_presence_of :full_name
validates_presence_of :email
validates_uniqueness_of :email, on: :create
validates_format_of :email, with: VALID_EMAIL_REGEX, on: :create
validates_presence_of :password, on: :create
validates_confirmation_of :password
end
organization.rb
class Organization < ActiveRecord::Base
authenticates_with_sorcery!
has_many :users, dependent: :destroy
accepts_nested_attributes_for :users, :allow_destroy => true
validates_presence_of :name
end
integration_spec.rb
require 'rails_helper'
describe "Shopping Cart Requests" do
let!(:user) { Fabricate(:user) }
before(:each) do
login_user_post("admin#example.com", "password")
end
context "when I visit the shopping cart" do
it " show the logged in users' cart items " do
#Test stuff
end
end
end
user_fabricator.rb
Fabricator(:user) do
organization { Fabricate(:organization) }
email { "admin#example.com" }
password { "password" }
full_name { Faker::Name.name }
is_admin { true }
salt { "asdfghjkl123456789" }
crypted_password { Sorcery::CryptoProviders::BCrypt.encrypt("secret", "asdasdastr4325234324sdfds") }
activation_state { 'active' }
end
organization_fabricator.rb
Fabricator(:organization) do
name { Faker::Company.name }
website { Faker::Internet.url }
description { Faker::Lorem.paragraph }
access_code { Faker::Internet.password(10, 20) }
end
Here's the error i am getting when running the test:
Failure/Error: let!(:user) { Fabricate(:user) }
NoMethodError:
undefined method `crypted_password' for #<Organization:0x007f80ee0a44e0>
# ./spec/features/integration_spec.rb:4:in `block (2 levels) in <top (required)>'
You have authenticates_with_sorcery! in your Organization app.
If you don't intend to authenticate Organization, you should remove that line.
Cheers

Rspec adds extra default error

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.

Resources