Updated
I'm trying to get Factory Girl to fill my "Release Date" field with a date, a random date, frankly any date right now because I keep getting " Validation failed: Release date can't be blank" errors when I run my item_pages_spec.rb
After some help below, his is what I have in my factories.rb for item pages but I've tried a lot of different things now.
factory :item do
sequence(:name) { |n| "Item #{n}" }
release_date { rand(1..100).days.from_now }
end
Ideally it would be a line that creates different random dates for each factory made instance of an item.
The release date can't be blank because I have validates :release_date, presence: true in my item model. Ideally I'd have a validation there that makes sure any date supplied IS a date but also accepts NIL because I won't always have a date available.
Any help much appreciated. I couldn't find anything specific online about factory girl and dates.
Model
class Item < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
validates :release_date, presence: true
end
Item_pages_spec.rb
require 'spec_helper'
describe "Item pages" do
subject { page }
describe "Item page" do
let(:item) { FactoryGirl.create(:item) }
before { visit item_path(item) }
it { should have_content(item.name) }
it { should have_title(item.name) }
end
end
The Faker gem has a very nice method for this:
Faker::Date.between(2.days.ago, Date.today)
This will set a date between now and 2 years from now, adjust params (or extract method) if needed:
factory :item do
sequence(:name) { |n| "Item #{n}" }
release_date do
from = Time.now.to_f
to = 2.years.from_now.to_f
Time.at(from + rand * (to - from))
end
end
Related
I am testing model using rspec and factory Girl
My model
class Currency < ActiveRecord::Base
has_many :countries
validates :name, presence: true
validates :name, uniqueness: true
before_destroy :safe_to_delete
def safe_to_delete
countries.any? ? false : true
end
end
My factory girl
FactoryGirl.define do
factory :currency, class: 'Currency' do
sequence(:name) { |i| "Currency-#{i}" }
end
end
My currency_spec.rb is
require 'rails_helper'
describe Currency , type: :model do
let(:currency) { create(:currency) }
let(:currency1) { create(:currency) }
let(:country) { create(:country) }
describe 'associations' do
subject {currency}
it { should have_many(:countries) }
end
describe 'validations' do
subject {currency}
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name) }
end
describe 'method save_to_delete' do
context 'case false' do
before { country.update_column(:currency_id, currency.id) }
subject { currency.destroy }
it { is_expected.to be_falsy }
end
context 'case true' do
before { country.update_column(:currency_id, currency1.id) }
subject { currency.destroy }
it { is_expected.to be_truthy }
end
end
end
The error is:
Failure/Error: let(:currency) { create(:currency) }
ActiveRecord::RecordInvalid:
A validação falhou: Name não está disponível
Even though I disable the presence and uniqueness validations in the model, the problem continues
Who can help me
Did you properly create the migration to include the name on currencies at database level?
Because I created the migration here locally and the tests passed.
Please take a look on the below code.
It is what I did locally and is working here!
1. Migration
file: db/migrate/2021XXXXXXXXXX_create_currencies.rb
class CreateCurrencies < ActiveRecord::Migration
def change
create_table :currencies do |t|
t.string :name
end
end
end
2. Model
app/models/currency.rb
class Currency < ActiveRecord::Base
has_many :countries
validates :name, presence: true, uniqueness: true # Can be oneliner ;)
before_destroy :safe_to_delete
def safe_to_delete
countries.empty? # Much simpler, right? ;)
end
end
3. Factory
spec/factories/currency.rb
FactoryGirl.define do
factory :currency do
sequence(:name) { |i| "Currency-#{i}" }
end
end
4. Tests
spec/models/currency_spec.rb
require 'rails_helper'
describe Currency, type: :model do
let(:currency1) { create(:currency) }
let(:currency2) { create(:currency) }
let(:country) { create(:country) }
describe 'associations' do
subject { currency1 }
it { should have_many(:countries) }
end
describe 'validations' do
subject { currency1 }
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name) }
end
describe 'when the currency is being deleted' do
context 'with countries associated' do
before { country.update_column(:currency_id, currency1.id) }
subject { currency1.destroy }
it { is_expected.to be_falsy }
end
context 'with no countries associated' do
before { country.update_column(:currency_id, currency2.id) }
subject { currency1.destroy }
it { is_expected.to be_truthy }
end
end
end
Test Execution
Finally, the tests should work correctly with the above setup!
spec/models/currency_spec.rb
rspec spec/models/currency_spec.rb
D, [2021-03-06T03:31:03.446070 #4877] DEBUG -- : using default configuration
D, [2021-03-06T03:31:03.449482 #4877] DEBUG -- : Coverband: Starting background reporting
.....
Top 5 slowest examples (0.10688 seconds, 11.4% of total time):
Currency when the currency is being deleted with countries associated should be falsy
0.04095 seconds ./spec/models/currency_spec.rb:23
Currency associations should have many countries
0.03529 seconds ./spec/models/currency_spec.rb:10
Currency when the currency is being deleted with no countries associated should be truthy
0.01454 seconds ./spec/models/currency_spec.rb:29
Currency validations should validate that :name cannot be empty/falsy
0.00812 seconds ./spec/models/currency_spec.rb:15
Currency validations should validate that :name is case-sensitively unique
0.00797 seconds ./spec/models/currency_spec.rb:16
Finished in 0.93948 seconds (files took 8.04 seconds to load)
5 examples, 0 failures
All tests passed ✅
I would like to test my models but all informations that I could find seems to be outdated. My goal is to test each individual validation.
My model:
class Author < ActiveRecord::Base
has_and_belongs_to_many :books
before_save :capitalize_names
validates :name, :surname, presence: true, length: { minimum: 3 },
format: { with: /[a-zA-Z]/ }
private
def capitalize_names
self.name.capitalize!
self.surname.capitalize!
end
end
and my factorygirl define:
FactoryGirl.define do
factory :author do |f|
f.name { Faker::Name.first_name }
f.surname { Faker::Name.last_name }
end
end
So now, I want to test whether name is not shorter than 3 characters.
My context:
context 'when first name is too short' do
it { expect( FactoryGirl.build(:author, name: 'Le')).to
be_falsey }
end
I know it's invalid because of [FactoryGirl.build(:author, name: 'Le')] returns hash instead of boolean value. So now, how should I test it? What matcher should I use?
[SOLVED]
Use be_valid instead of be_falsey. Now it should look like :
context 'when first name is too short' do
it { expect( FactoryGirl.build(:author, name: 'Le')).not_to
be_valid }
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
I'm building a daily deal rails app and I have followed M. Hartl tutorial to set some rspec tests.
For users they work perfectly.
But now I have used it for the model Eals and all are passing, when they shouldn't. For example, in my models, I put that titles can't be longer than 200 characters (note:on my view, when I try to set titles longer than this, it works and alerts me it's not possible)
But when I do tests no matter if I try for the title character length's test with long = "a" * 50, a * 201 or even a * 10000 in the title test, it always pass! There is a big problem I don't manage to find.
And actually all the other tests have the same problem: they always pass!
Here is my models/deal.rb
class Deal < ActiveRecord::Base
belongs_to :admin_user
attr_accessible :url_path,
:country,
:title,
:description,
:twitter_msg,
:admin_user_id
validates :url_path,
presence: true,
uniqueness: { :case_sensitive => false }
validates :country,
:inclusion => { :in => ['France', 'Germany', 'United States'],
:message => "%{value} is not a valid country. " }
validates :title,
presence: true,
length: { maximum: 200,
:message => "Your title has %{value} characters but must be shorter than 200 characters" }
validates :description,
presence: true,
length: { maximum: 500,
:message => "Your title has %{value} characters but must be shorter than 500 characters" }
validates :twitter_msg,
presence: true,
uniqueness: { :case_sensitive => false }
validates :admin_user_id, presence: true
And my deal_spec.rb:
require 'spec_helper'
describe Deal do
let(:admin_user) { FactoryGirl.create(:admin_user) }
before (:each) do
#attr = { url_path: "lorem ipsum",
country:"France",
title: "lorem ipsum",
description:"lorem ipsum",
twitter_msg:"lorem ipsum",
}
end
it { should respond_to(:url_path) }
it { should respond_to(:country) }
it { should respond_to(:title) }
it { should respond_to(:description) }
it { should respond_to(:twitter_msg) }
describe "title test" do
it "should reject deals with title that is too long" do
long = "a" * 50
hash = #attr.merge(:title => long)
Deal.new(hash).should_not be_valid
end
[other tests]
end #end of title test
If anybody can help me understand that, that would be great, I have been spending hours without any clue.
After following sb advice, I changed my test with
Deal.new(hash).should have(1).error_on(:title)
describe "test" do
it "should reject games with title that is too long" do
long = "a" * 250
hash = #attr.merge(:title => long)
Game.new(hash).should have(1).error_on(:title)
end
end
But now it's passing all the time, i.e it's telling me I have one error on title no matter if I put long= "a" * 5, long="a" * 300...
This is not the correct way to test validation using RSpec because it doesn't tell you why the object is invalid. It may be invalid because you're missing a totally different attribute that the one you're testing for. You should be using the have(x).errors_on(y) assertion:
Deal.new(hash).should have(1).error_on(:title)
I recommend using shoulda_matchers for testing such things
Could you please try the following code? It should give you a hint what attribute is invalid (and why):
it "..." do
d = Deal.new(#attr)
d.valid?
puts d.errors.full_messages
end
admin_user_id is not in your #attr hash. However it's mandatory on your Deal model - so your test always passes because the new deal is not valid. Nothing to do with the length of the title.
Am swinging between Shoulda and Rspec these days. I have read and played around a fair bit with RSpec but not that much with Shoulda. I find Shoulda's one line assertions easier to read and the test looks cleaner. But when I can't figure out how write a particular assertion in Shoulda I switch to RSpec. Not very happy about it though.
So here is what I did today. I wrote some custom validations for my model Course. A course has a start_date and an end_date. There are a few rules around it.
start_date and end_date are both mandatory
start_date cannot be later than today
end_date cannot be before the start_date
I know there are quiet a few gems out there that could have done it for me. But coz I am new I thought it might be good idea to do it myself and learn as I go.
So this is what my model looks like
class Course < ActiveRecord::Base
belongs_to :category
has_many :batches, :dependent => :destroy
accepts_nested_attributes_for :batches, :reject_if => lambda {|a| a[:code].blank?}, :allow_destroy => true
has_and_belongs_to_many :students, :uniq => true
validates_presence_of :name, :course_code, :total_seats
validates_uniqueness_of :category_id, :scope => [:name, :course_code]
validates :start_date, :presence => true, :course_start_date=>true
validates :end_date, :presence => true, :course_end_date=>true
end
My custom validations are as follows
class CourseEndDateValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
if object.errors[attribute].blank? && object.errors[:start_date].blank?
if value < object.start_date
object.errors[attribute] << "cannot be later than start date"
end
end
end
end
class CourseStartDateValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
if object.errors[attribute].blank?
if value < DateTime.now.to_date
object.errors[attribute] << "cannot be later than today"
end
end
end
end
And following is my course_spec
require 'spec_helper'require 'date'
describe Course do
context 'validations' do
it { should validate_presence_of(:name)}
it { should validate_presence_of(:course_code)}
it { should validate_presence_of(:start_date)}
it { should validate_presence_of(:end_date)}
it { should validate_presence_of(:total_seats)}
date = DateTime.now.to_date
it { should allow_value(date).for(:start_date) }
it { should_not allow_value(date - 10 ).for(:start_date) }
it {should allow_value(date + 10).for(:end_date)}
end
context 'associations' do
it { should belong_to(:category)}
it { should have_many(:batches).dependent(:destroy)}
it { should have_and_belong_to_many(:students) }
end
it " end date should not be before course start date" do
course = FactoryGirl.build(:course, :end_date=>'2011-12-10')
course.should be_invalid
end
end
Now before I wrote the last "it" block using Rspec I had something like this in my validations context
context 'validations' do
it { should validate_presence_of(:name)}
it { should validate_presence_of(:course_code)}
it { should validate_presence_of(:start_date)}
it { should validate_presence_of(:end_date)}
it { should validate_presence_of(:total_seats)}
date = DateTime.now.to_date
it { should allow_value(date).for(:start_date) }
it { should_not allow_value(date - 10 ).for(:start_date) }
it { should allow_value(date + 10).for(:end_date)}
it { should_not allow_value(date - 10).for(:end_date)} # <-------------------
end
And I got the following failure
Failures:
1) Course validations
Failure/Error: it { should_not allow_value(date - 10).for(:end_date)}
Expected errors when end_date is set to Fri, 9 Dec 2011, got errors: ["name can't be blank (nil)", "course_code can't be blank (nil)", "total_seats can't be blank (nil)", "start_date can't be blank (nil)"]
Am not sure what am I doing wrong here. Is it my custom validation code that is not correct or I need to setup something before I run the last assertion so that start_date is not nil when testing end_date?
The validations work fine in the view. I mean I get the right validation errors depending on the kind of data I input. But am test is failing. I have been looking at this for a while now but cannot figure out what exactly am I doing wrong.
I think you could tackle this in one of two two ways:
Either you need to place you date = DateTime.now.to_date into to before(:each) block.
context 'validations' do
before(:each) { date = DateTime.now.to_date }
it { should allow_value(date).for(:start_date) }
it { should_not allow_value(date - 10 ).for(:start_date) }
it { should allow_value(date + 10).for(:end_date)}
it { should_not allow_value(date - 10).for(:end_date)}
end
Or you could use the rails date helpers.
context 'validations' do
it { should allow_value(Date.today).for(:start_date) }
it { should_not allow_value(10.days.ago).for(:start_date) }
it { should allow_value(10.days.from_now).for(:end_date)}
it { should_not allow_value(10.days.ago).for(:end_date)}
end
#nickgrim already answered the question, but I want to add a comment. The point of describe and it is to encourage sentences that start with the words "describe" and "it". In your example, you've got this:
it " end date should not be before course start date" do
# ...
"it end date ...." is not a sentence. Please write that as something like:
it "validates that end date should be >= start date" do