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
Related
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'm developing a Rails5 API and I'm getting an ActiveRecord::AssociationTypeMismatch:
error when testing my model associations. Here's the code:
user.rb:
class User < ApplicationRecord
acts_as_paranoid
has_secure_password
has_many :contracts
end
service.rb:
class Service < ApplicationRecord
acts_as_paranoid
has_many :contracts
end
contract.rb:
class Contract < ApplicationRecord
acts_as_paranoid
belongs_to :user
belongs_to :service
validates_presence_of :user
validates :service, presence: true, uniqueness: { scope: :user_id }
end
contract_spec.rb:
RSpec.describe Contract, type: :model do
let(:contract) { create(:contract) }
describe 'Validations' do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:service) }
it { is_expected.to validate_uniqueness_of(:service).scoped_to(:user_id) }
end
describe 'Associations' do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:service) }
end
end
An my tests fails with:
1) Contract Validations should validate that :service is case-sensitively unique within the scope of :user_id
Failure/Error: it { is_expected.to validate_uniqueness_of(:service).scoped_to(:user_id) }
ActiveRecord::AssociationTypeMismatch:
Service(#47073011870820) expected, got String(#47072966778980)
I've seem some other questions that look like this but none that had a working solution for this problem. Any help would be appreciated.
I had run into the same issue. One workaround is defining a subject
describe 'Validations' do
subject { create(:contract) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:service) }
it { is_expected.to validate_uniqueness_of(:service).scoped_to(:user_id) }
end
Source: validates_uniqueness_of does not work when record is new and attribute is an association
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.
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.