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) }
Related
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
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
I am testing my app ( Rails 5) with rspec capybara and factory girl I have the following error...
I am not sure what's happening... I am very new with rspec I hope you could help me :) thank you
Randomized with seed 41137
An error occurred in a `before(:suite)` hook.
Failure/Error: FactoryGirl.lint
SystemStackError:
stack level too deep
You will find my code below:
factories.rb
FactoryGirl.define do
factory :event do
name {Faker::Friends.character}
total_price 50
participant
end
factory :participant do
first_name { Faker::Name.first_name }
salary 900
event
end
end
event.rb
class Event < ApplicationRecord
has_many :participants, inverse_of: :event
validates :participants, presence: true
validates :name, presence: true, length: {minimum: 2}
validates :total_price, presence: true
accepts_nested_attributes_for :participants, reject_if: :all_blank, allow_destroy: true
def total_salary
all_salary = []
participants.each do |participant|
all_salary << participant.salary
end
return #total_salary = all_salary.inject(0,:+)
end
end
event_spec.rb
require 'rails_helper'
describe Event do
it { should have_many(:participants) }
it { should validate_presence_of(:participants) }
it { should validate_presence_of(:name) }
it { should validate_presence_of(:total_price) }
describe "#total_salary" do
it "should return the total salary of the participants" do
partcipant_1 = create(:participant, salary: 2000)
partcipant_2 = create(:participant, salary: 3000)
expect(partcipant_1.salary + partcipant_2.salary).to eq(5000)
end
end
end
edit
In my participant model I had to add optional: true
belongs_to :event, option: true
so fabriciofreitag suggestion works well :)
Let's take a look at your factories:
FactoryGirl.define do
factory :event do
name {Faker::Friends.character}
total_price 50
participant
end
factory :participant do
first_name { Faker::Name.first_name }
salary 900
event
end
end
In this scenario, the creation of event will create a participant, that will create an event, that will create a participant. and so on, in an infinite loop (stack level too deep).
Perhaps you could change it to something like this:
FactoryGirl.define do
factory :event do
name {Faker::Friends.character}
total_price 50
participants { create_list(:participant, 3, event: self) }
end
factory :participant do
first_name { Faker::Name.first_name }
salary 900
end
end
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
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.