I am using FactoryGirl and populate unique attribute whenever the model is made. The problem with my model form is that there are only 4 different types available for form_type attribute. So I need to reset the sequence everytime I run tests. Like below, I user before do block to call FactoryGirl.reload. However, I saw an article saying it is anti-pattern to FactoryGirl. What is the best way to reset the sequence in FactoryGirl instead of calling FactoryGirl.reload before every test?
Here is my forms.rb Factorygirl file,
FactoryGirl.define do
factory :form do
association :user
sequence :form_type do |n|
Form.form_types.values[n]
end
end
end
Here is my form.rb model file:
class Form < ActiveRecord::Base
belongs_to :user, required: true
enum form_types: { :a => "Form A", :b => "Form B", :c => "Form C", :d => "Form D"}
validates :form_type, presence: true
validates :form_type, uniqueness: {scope: :user_id}
end
Here is my forms_controller_spec.rb file:
require 'rails_helper'
RSpec.describe FormsController, type: :controller do
login_user
let(:form) {
FactoryGirl.create(:form, user: #current_user)
}
let(:forms) {
FactoryGirl.create_list(:form , 3, user: #current_user)
}
let(:form_attributes) {
FactoryGirl.attributes_for(:form, user: #current_user)
}
describe "GET #index" do
before do
FactoryGirl.reload
end
it "loads all of the forms into #forms" do
get :index
expect(assigns(:forms)).to match_array(#forms)
end
end
end
Hm, it seems like the purpose of FG sequences is to ensure unique numbers. Or at least that's what I've used it for. You may be able to hack into FG if this is what you really want.
This question may help.
How can I reset a factory_girl sequence?
Related
I have 2 models sharing a simple belong_to/has_many relation: Room belongs to Building
I created a custom validator called total_number_rooms_limited_to_15 that ensures I can't create more than 15 rooms for a given Building.
class Room < ActiveRecord::Base
# -- Relationships --------------------------------------------------------
belongs_to :admin_user, :foreign_key => 'admin_user_id'
belongs_to :building, :foreign_key => 'building_id'
# -- Validations ----------------------------------------------------------
validates :room_filename,
presence: true
# associated models primary key validates
validates :admin_user_id,
presence: true
validates :building_id,
presence: true
validate :total_number_rooms_limited_to_15
private
def total_number_rooms_limited_to_15
errors[:base] << "There can't be more than 15 rooms. There are already 15 .
<br/>Please remove another one or drop trying adding this one.".html_safe
unless ( self.building.rooms.count < 15 )
end
But the problem is that after creating this new validator, all my "usual" basic tests fail.
require 'spec_helper'
RSpec.describe Room, type: :model do
before(:each) do
#attr = {
room_filename: "xyz"
}
end
# -- Models Tests --------------------------------------------------------
describe "tests on ST's models validations for room_filename" do
it { is_expected.to validate_presence_of(:room_filename) }
it { is_expected.not_to allow_value(" ").for(:room_filename) }
end
All give me the following error message:
1) Room tests on ST's models validations for room_filename should validate that :room_filename cannot be empty/falsy
Failure/Error:
errors[:base] << "There can't be more than 15 rooms. There are already 15 .
<br/>Please remove another one or drop trying adding this one.".html_safe unless ( self.building.rooms.count < 15 )
NoMethodError:
undefined method `rooms' for nil:NilClass
I tried adding inside #attr the attribute a associated "virtual" building but it not work out;, getting the same error message:
before(:each) do
#attr = {
room_filename: "xyz",
building_id: 1
}
ADDED INFO
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation, :except => %w(roles))
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
For custom validations you'll need to instantiate a new Room object in your tests. If you don't have something like factory_girl or fabrication in place to create objects for your test, you can do this:
before(:each) do
#admin_user = AdminUser.create!(...attributes)
#building = Building.create!(...attributes)
#room = Room.create!(building_id: #building.id, admin_user_id: #admin_user.id)
end
Then make sure you're calling your validation on the instance instead of your Room class:
def total_number_rooms_limited_to_15
errors[:base] << "There can't be more...".html_safe
unless ( building.present? && building.rooms.count < 15 )
end
cannot seem to get my validators to work to ensure all attributes are present to allow a User to be created. Basic User with 2 attributes
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
end
tests to check that name and email are present when created. these #pass
RSpec.describe User, type: :model do
context 'validations' do
subject { FactoryGirl.build(:user) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_presence_of(:name) }
it "fails to create user unless both are present" do
expect { User.create(:name => 'jo bloggs1', :noemail => 'c#c.co')}.to raise_error(ActiveModel::UnknownAttributeError)
end
end
end
but if i try and create model with a missing attribute no error is raised
it "fails to create user unless both are present" do
expect { User.create(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
end
result
1) User validations fails to create user unless both are present
Failure/Error: expect { User.create(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
expected ActiveModel::MissingAttributeError but nothing was raised
# ./spec/models/user_spec.rb:12:in `block (3 levels) in <top (required)>'
fyi, FactoryGirl
FactoryGirl.define do
factory :user do
name "MyString"
email "MyString"
end
end
i have tried clever stuff like
class User < ApplicationRecord
# before_create :run_it
after_initialize :all_present?
validates :name, presence: true
validates :email, presence: true
private
def all_present?
if (#email.nil? || #name.nil?)
raise ActiveModel::MissingAttributeError.new()
end
end
end
but cannot seem to raise these manually...?
what am i doing wrong?
tx all
Ben
The problem is that there are 2 methods, create and create!. The first, create
The resulting object is returned whether the object was saved successfully to the database or not
Whereas with create!:
Raises a RecordInvalid error if validations fail, unlike Base#create
So, create fails silently and doesn't raise any exceptions, but you can still inspect the instance and see that it's a new record and has errors and such, and create! fails noisily, by raising the error you are expecting it to raise. In short, your test should be:
it "fails to create user unless both are present" do
expect { User.create!(:name => 'jo bloggs1')}.to raise_error(ActiveModel::MissingAttributeError)
end
I want to test the mailer in my application to make sure it is doing what I want it to do.
class LessonMailer < ApplicationMailer
def send_mail(lesson)
#lesson = lesson
mail(to: lesson.student.email,
subject: 'A lesson has been recorded by your tutor')
end
end
This is my test in the spec/mailers directory
require "rails_helper"
RSpec.describe LessonMailer, :type => :mailer do
describe "lesson" do
let( :student ){ FactoryGirl.create :user, role: 'student', givenname: 'name', sn: 'sname', email: 'test#sheffield.ac.uk' }
let( :lesson ){ FactoryGirl.create :lesson, student_id: 2 }
let( :mail ){ LessonMailer.send_mail( lesson ).deliver_now
it "renders the headers" do
expect(mail.subject).to eq("A lesson has been recorded")
expect(mail.to).to eq(["to#example.ac.uk"])
expect(mail.from).to eq(["no-reply#example.ac.uk"])
end
it "renders the body" do
expect(mail.body.encoded).to match("A lesson form has been recorded")
end
end
end
I want to test that the 'send_mail' method is working the way I want it to however, I am getting this error. How do I go about solving this problem ? Thank you.
NoMethodError:
undefined method `email' for nil:NilClass
# ./app/mailers/lesson_mailer.rb:4:in `send_mail'
So, with FactoryGirl, you just need to instantiate the different objects that you need. Reading your code, it seems clear that a lesson has a student and that students have an email. So go ahead and create everything you need and then call your method. You can do something like this:
# Here's the student factory (for this use case, you'll probably want to make it more general)
FactoryGirl.define do
factory :user do
role 'student'
givenname 'name'
sn 'sname'
email 'test#sheffield.ac.uk'
end
end
# Here's your test
require "rails_helper"
RSpec.describe LessonMailer, :type => :mailer do
describe "lesson" do
let( :student ){ create :student, email: 'test_email#example.com' }
let( :lesson ){ create :lesson, student: student }
let( :mail ){ LessonMailer.send_mail( lesson ) }
it ' ... ' do
...
end
end
end
You'll need to let the test environment know that you want the emails to be delivered to the ActionMailer::Base.deliveries array. To do this, make sure that
config.action_mailer.delivery_method = :test
is set in your config/environments/test.rb
One last thing, I'm not sure if you'll need it, but you might have to call the mailer with, .deliver_now.
Like this:
let( :mail ){ LessonMailer.send_mail( lesson ).deliver_now }
... or it may not send. I can't remember off the top.
Let me know how it goes.
I have written an rspec test for checking invalid characters in my permalink:
describe "formatting permalinks when creating a page" do
it "does not allow crazy characters" do
page = create(:page, permalink: '#$%^&*first-title')
expect(page).to have(1).errors_on(:permalink)
end
end
In my page.rb model, I have this validation implemented to make it pass:
class Page < ActiveRecord::Base
validates :permalink, format: {:with => /\A[a-zA-Z-]+\Z/, :on => :save!}
before_create :create_slug
def create_slug
self.permalink = self.permalink.parameterize
end
end
But I get his error:
expected 1 errors on :permalink, got 0
What am I doing wrong? How do I fix this?
Your create_slug calls parameterize. Because it's run as a before_create, it changes '#$%^&*first-title' to "first-title".
Also, according to the docs, on: should only be used with create and update, so I'm not sure this is running at all.
I'm new to RSpec, and trying to get my head around using Factory Girl with associations in controller specs. The difficulty is:
it's necessary to use "attributes_for" in functional tests
attributes_for "elides any associations"
So if I have models like this:
class Brand < ActiveRecord::Base
belongs_to :org
validates :org, :presence => true
end
class Org < ActiveRecord::Base
has_many :brands
end
And a factory like this:
FactoryGirl.define do
factory :brand do
association :org
end
end
This controller spec fails:
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post :create, brand: attributes_for(:brand)
}.to change(Brand, :count).by(1)
end
end
end
(And if I comment out "validates :org, :presence => true" it passes)
There are a number of solutions suggested and I think I have been making simple errors which have meant that I have not been able to get any of them to work.
1) Changing the factory to org_id per a suggestion on this page failed a number of tests with "Validation failed: Org can't be blank"
FactoryGirl.define do
factory :brand do
org_id 1002
end
end
2) Using "symbolize_keys" looks promising. Here and here it is suggested to use code like this:
(FactoryGirl.build :position).attributes.symbolize_keys
I'm not sure how to apply this in my case. Below is a guess that doesn't work (giving the error No route matches {:controller=>"brands", :action=>"{:id=>nil, :name=>\"MyString\", :org_id=>1052, :include_in_menu=>false, :created_at=>nil, :updated_at=>nil}"}):
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post build(:brand).attributes.symbolize_keys
}.to change(Brand, :count).by(1)
end
end
end
Update
I almost got this working with Shioyama's answer below but got the error message:
Failure/Error: post :create, brand: build(:brand).attributes.symbolize_keys
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: id, created_at, updated_at
So following this question I changed it to:
post :create, brand: build(:brand).attributes.symbolize_keys.reject { |key, value| !Brand.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Which worked!
In your solution 2), you have not passed an action to post which is why it is throwing an error.
Try replacing the code in that expect block to:
post :create, brand: build(:brand).attributes.symbolize_keys