I'm developing a blog in Rails and I'm stuck when I was trying to test the default scope I added to the Post model in order to have the posts in descending order of their creation date.
Post code:
class Post < ActiveRecord::Base
attr_accessible :content, :name, :title
validates :title, presence: true,uniqueness: true
validates :name, presence: true
validates :content, presence: true
default_scope order: "posts.created_at DESC"
end
Rspec code:
describe "Posts descending order of creation date" do
let(:older_post) do
FactoryGirl.create(:post, created_at: 1.day.ago)
end
let(:newer_post) do
FactoryGirl.create(:post, created_at: 1.hour.ago)
end
it "should have the 2 posts in desc order" do
Post.all.should == [newer_post, older_post]
end
end
FactoryGirl definition
FactoryGirl.define do
factory :post do
sequence(:title) { |n| "A book #{n}" }
name "Johnny"
content "Lorem Ipsum"
end
end
The output
.....F...
Failures:
1) Post Posts descending order of creation date should have the 2 posts in desc order
Failure/Error: Post.all.should == [newer_post, older_post]
expected: [#<Post id: 1, name: "Johnny", title: "A book 1", content: "Lorem Ipsum", created_at: "2013-05-01 14:44:45", updated_at: "2013-05-01 15:44:45">, #<Post id: 2, name: "Johnny", title: "A book 2", content: "Lorem Ipsum", created_at: "2013-04-30 15:44:45", updated_at: "2013-05-01 15:44:45">]
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[#<Post id: 1, name: "Johnny", title: "A book 1", content: "Lorem Ipsum", created_at: "2013-05-01 14:44:45", updated_at: "2013-05-01 15:44:45">,
- #<Post id: 2, name: "Johnny", title: "A book 2", content: "Lorem Ipsum", created_at: "2013-04-30 15:44:45", updated_at: "2013-05-01 15:44:45">]
+[]
# ./spec/models/post_spec.rb:54:in `block (3 levels) in <top (required)>'
Finished in 1.03 seconds
9 examples, 1 failure
Failed examples:
rspec ./spec/models/post_spec.rb:53 # Post Posts descending order of creation date should have the 2 posts in desc order
I also want to mention that when I type Post.all in the Rails console, I get the records in descending order ( so as I wanted them).
Can someone give me a suggestion on what the problem might be?
Please know that let is evaluated lazily in RSpec. This often creates problem in such scenarios where ordering is concerned.
Try these two alternatives:
describe "Posts descending order of creation date" do
let!(:older_post) do
FactoryGirl.create(:post, created_at: 1.day.ago)
end
let!(:newer_post) do
FactoryGirl.create(:post, created_at: 1.hour.ago)
end
it "should have the 2 posts in desc order" do
Post.all.should == [newer_post, older_post]
end
end
Note, the use of let! instead of let.
Or, use before as:
describe "Posts descending order of creation date" do
it "should have the 2 posts in desc order" do
#older_post = FactoryGirl.create(:post, created_at: 1.day.ago)
#newer_post = FactoryGirl.create(:post, created_at: 1.hour.ago)
Post.all.should == [#newer_post, #older_post]
end
end
Do let me know if it works or not. :)
Related
Doing TDD
I facing a problem creating a book and assigning two authors with it. There is a failure in the creation. It says that there is an AssociationTypeMismatch: Author(#15100) expected, got nil which is an instance of NilClass(#40. I suppose that the format "authors: []" is not have been read well by something.
This is the test:
def test_has_many_and_belongs_to_mapping
apress = Publisher.find_by(name: 'Apress')
assert_equal 2, apress.books.size
book = Book.new(
title: 'Rails E-Commerce 3nd Edition',
authors: [
Author.find_by(first_name: 'Christian', last_name: 'Hellsten'),
Author.find_by(first_name: 'Jarkko', last_name: 'Laine')
],
published_at: Time.now,
isbn: '123-123-123-x',
blurb: 'E-Commerce on Rails',
page_count: 300,
price: 30.5
)
# apress.books << book
# apress.reload
# book.reload
# assert_equal 3, apress.books.size
# assert_equal 'Apress', book.publisher.name
end
This is the error:
MY ERD:
My schema:
I think what you are missing is a meaningful setup of data needed before this test runs. Even though those publishers and books may exist in your development environment/database, the test suite usually is configured to wipe the database clean between runs.
If you have fixtures defined, then you can reference them like this:
test/fixtures/publishers.yml
apress:
id: 1
name: Apress
test/fixtures/authors.yml
hellsten:
id: 1
first_name: Christian
last_name: Hellsten
laine:
id: 2
first_name: Jarkko
last_name: Laine
test/fixtures/books.yml
beginning_ruby:
title: Beginning Ruby
publisher_id: 1
ruby_recipes:
title: Ruby Recipes
publisher_id: 1
So that the test would look more like this:
def test_has_many_and_belongs_to_mapping
apress = publishers(:apress)
assert_equal 2, apress.books.size
book = Book.new(
title: 'Rails E-Commerce 3nd Edition',
authors: [
Author.find_by(first_name: 'Christian', last_name: 'Hellsten'),
Author.find_by(first_name: 'Jarkko', last_name: 'Laine')
],
published_at: Time.now,
isbn: '123-123-123-x',
blurb: 'E-Commerce on Rails',
page_count: 300,
price: 30.5
)
apress.books << book
apress.reload
book.reload
assert_equal 3, apress.books.size
assert_equal 'Apress', book.publisher.name
end
Or instead of using fixtures, you could create the content before the test:
So that the test would look more like this:
setup do
publisher = Publisher.create!(name: 'Apress')
publisher.books.create!(title: 'Beginning Ruby')
publisher.books.create!(title: 'Ruby Recipes')
Author.create!(first_name: 'Christian', last_name: 'Hellsten')
Author.create!(first_name: 'Jarkko', last_name: 'Laine')
end
def test_has_many_and_belongs_to_mapping
apress = Publisher.find_by(name: 'Apress')
assert_equal 2, apress.books.size
book = Book.new(
title: 'Rails E-Commerce 3nd Edition',
authors: [
Author.find_by(first_name: 'Christian', last_name: 'Hellsten'),
Author.find_by(first_name: 'Jarkko', last_name: 'Laine')
],
published_at: Time.now,
isbn: '123-123-123-x',
blurb: 'E-Commerce on Rails',
page_count: 300,
price: 30.5
)
apress.books << book
apress.reload
book.reload
assert_equal 3, apress.books.size
assert_equal 'Apress', book.publisher.name
end
That way your database has those values, and your find_by() calls should return data, and not nil.
I'm running into an issue where FactoryGirl appears to be creating extra records with a has_many relationship.
Given these models:
class NextAction < ActiveRecord::Base
has_many :next_actions_orders
has_many :orders, through: :next_actions_orders
end
class NextActionsOrder < ActiveRecord::Base
belongs_to :order
belongs_to :next_action
end
class Order < ActiveRecord::Base
has_many :next_actions_orders
has_many :next_actions, through: :next_actions_orders
end
And these factories:
FactoryGirl.define do
factory :next_action do
status :pending
trait :pickup do
next_actions_orders { FactoryGirl.create_list(:next_actions_order, 1) }
action_type :pickup
end
trait :multiple_pickups do
next_actions_orders { FactoryGirl.create_pair(:next_actions_order) }
action_type :pickup
end
end
end
FactoryGirl.define do
factory :next_actions_order do
order { FactoryGirl.create(:order) }
next_action
end
end
FactoryGirl.define do
factory :order do
status :pending
end
end
As you can see in the NextAction factory, I ran into an issue setting up the NextActionOrder association.
I would usually have used next_actions_orders { FactoryGirl.create(:next_actions_order) } but with the has_many :next_actions_orders, I was getting an undefined method 'each' for #<NextActionsOrder... error.
next_actions_orders { FactoryGirl.create_list(:next_actions_order, 1) } seems to work as a workaround. As shown below, this doesn't seem to be the cause of the issue, since it also exists from the create_pair example.
The real issue is this:
it 'create_list generates duplicate FactoryGirl records' do
puts NextAction.count # output: 0
pickup = create(:next_action, :pickup)
puts NextAction.count # output: 2
## binding.pry
end
Succinctly, the create(:next_action) calls seem to be generating 1 additional NextAction record than is required.
I used pry to inspect this and sure enough, you can see this.
With pry inserted at the first location shown above, queries produce the following:
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextAction.all
=> [#<NextAction id: 2, ... created_at: "2014-04-20 16:40:57", updated_at: "2014-04-20 16:40:57", status: 0>,
#<NextAction id: 3, ... created_at: "2014-04-20 16:40:57", updated_at: "2014-04-20 16:40:57", status: 0>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextActionsOrder.all
=> [#<NextActionsOrder id: 1, next_action_id: 3, order_id: 2>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> Order.all
=> [#<Order id: 2, created_at: "2014-04-20 16:40:57", updated_at: "2014-04-20 16:40:57", status: 0>]
Here, everything looks great, except for NextAction ID #2. It's not associated anywhere, it's just an orphan record that was created for some reason.
Here's what happens with create_pair:
it 'create_pair generates duplicate FactoryGirl records' do
puts NextAction.count # output: 0
pickups = create(:next_action, :multiple_pickups)
puts NextAction.count # output: 3
## binding.pry
end
With pry inserted as shown, the same queries produce:
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextAction.all
=> [#<NextAction id: 4, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>,
#<NextAction id: 5, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>,
#<NextAction id: 6, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextActionsOrder.all
=> [#<NextActionsOrder id: 2, next_action_id: 6, order_id: 3>,
#<NextActionsOrder id: 3, next_action_id: 6, order_id: 4>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> Order.all
=> [#<Order id: 3, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>,
#<Order id: 4, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>]
Again, everything looks good, except now we have 2 orphan NextAction records - IDs #4 and #5.
Any ideas? Thanks so much!
It's because of the next_action in the factory :next_actions_order, which creates an additional next_action...
I would rewrite the next_action factory using
after(:build) do |next_action, evaluator|
next_action.orders << build(:order)
end
or
after(:build) do |next_action, evaluator|
next_action.orders << build_list(:order, evaluator.orders_count)
end
if you need more than one.
This way the entire chain is created at once with all cross references filled in as they should! (and without doubles!)
I've the following rspec unit test:
require 'spec_helper'
describe Article do
describe ".recents" do
it "includes articles created less than one week ago" do
article = Article.create(created_at: Date.today - 1.week + 1.second)
expect(Article.recents).to eql([article])
end
it "excludes articles published at midnight one week ago" do
article = Article.create!(:created_at => Date.today - 1.week)
expect(Article.recents).to be_empty
end
end
end
and the Articlemodel:
class Article < ActiveRecord::Base
attr_accessible :description, :name, :price, :created_at
scope :recents, where('created_at <= ?', 1.week.ago)
end
when I run my tests I get:
1) Article.recents includes articles created less than one week ago
Failure/Error: expect(Article.recents).to eql([article])
expected: [#<Article id: 60, name: nil, description: nil, price: nil, created_at: "2012-11-14 00:00:01", updated_at: "2012-11-21 10:12:33", section_id: nil>]
got: [#<Article id: 60, name: nil, description: nil, price: nil, created_at: "2012-11-14 00:00:01", updated_at: "2012-11-21 10:12:33", section_id: nil>]
(compared using eql?)
Diff:#<ActiveRecord::Relation:0x007ff692bce158>.==([#<Article id: 60, name: nil, description: nil, price: nil, created_at: "2012-11-14 00:00:01", updated_at: "2012-11-21 10:12:33", section_id: nil>]) returned false even though the diff between #<ActiveRecord::Relation:0x007ff692bce158> and [#<Article id: 60, name: nil, description: nil, price: nil, created_at: "2012-11-14 00:00:01", updated_at: "2012-11-21 10:12:33", section_id: nil>] is empty. Check the implementation of #<ActiveRecord::Relation:0x007ff692bce158>.==.
# ./spec/models/article_spec.rb:7:in `block (3 levels) in <top (required)>'
Could someone please help me to figure out what's the error in my test?
It seems good for me.
You are comparing an activerecord relation (Article.recents) to an array ([article]), which is why the expectation is failing. (It looks like they are the same in the spec results because inspect converts the relation into an array before printing it out.)
Change your first expectation to this:
expect(Article.recents.to_a).to eql([article])
I am taking over a code base and am trying to run the tests. I am
somewhat new to RSpec so this might be a trivial problem.
Basically I can tell that the fixtures are not getting loaded. All 100
tests fail with a similar error.
But I don't know why. Below is the code. Where can I start looking?
In spec_helper.rb, which I know is running, I see:
Spec::Runner.configure do |config|
config.global_fixtures = :all
end
One of the tests in spec/controllers/downloads_controller_spec.rb
is below. I know it is running and I know that before the 'describe',
Partner.count == 0, so no fixture.
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe DownloadsController do
integrate_views
describe "when PDF is ready" do
before(:each) do
#registrant = Factory.create(:step_5_registrant)
stub(#registrant).merge_pdf { `touch #{#registrant.pdf_file_path}`
}
#registrant.generate_pdf
#registrant.save!
end
it "provides a link to download the PDF" do
get :show, :registrant_id => #registrant.to_param
assert_not_nil assigns[:registrant]
assert_response :success
assert_template "show"
assert_select "span.button a[target=_blank]"
assert_select "span.button a[onclick]"
end
after(:each) do
`rm #{#registrant.pdf_file_path}`
end
end
And here is what in various directories:
spec/fixtures/partners.yml - which contains 2 yaml records:
sponsor:
id: 1
username: rtv
email: rocky#example.com
crypted_password:
"c8e5b51b237344fe0e72539af0cac7197f094a5e933ffacf6e7fa612363c5933f520710c6427ac31fc4c68a2d7bb48eae601c74b96e7838f9ca1a0740b67576a"
password_salt: "Y4PPzYx2ert3vC0OhEMo"
name: Rocky
organization: Rock The Vote
url: http://rockthevote.com
address: 123 Wherever
city: Washington
state_id: 9
zip_code: 20001
phone: 555-555-1234
survey_question_1_en: "What school did you go to?"
survey_question_2_en: "What is your favorite musical group?"
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
# TODO: remove partner 2 in production
partner:
id: 2
username: bull_winkle
email: bull_winkle#example.com
crypted_password:
"c8e5b51b237344fe0e72539af0cac7197f094a5e933ffacf6e7fa612363c5933f520710c6427ac31fc4c68a2d7bb48eae601c74b96e7838f9ca1a0740b67576a"
password_salt: "Y4PPzYx2ert3vC0OhEMo"
name: Bullwinkle
organization: Bullwinkle, Inc.
url: http://example.com
address: 123 Wherever
city: Washington
state_id: 9
zip_code: 20001
phone: 555-555-1234
survey_question_1_en: "What school did you go to?"
survey_question_2_en: "What is your favorite musical group?"
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
Maybe this is done automatically now and you have solved it, but have you checked that your "spec/spec_helper.rb" contains:
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
Cheers
In my project, the user will have many items, which have an onshelf_at attribute default to DateTime.now at its creation.
# item.rb
class Item < ActiveRecord::Base
before_create :calculate_onshelf_time
default_scope :order => 'items.onshelf_at DESC'
def calculate_onshelf_time
self.onshelf_at = DateTime.now
end
end
In the user model test, I tried to convince myself the retrieved order is indeed items.onshelf_at DESC. So I made the following snippet, but the result turned out to be reverse. (namely [#item1, #item2])
# spec/models/user_spec.rb
before :each do
#user = User.create(#attr)
#item1 = Factory(:item, :owner=>#user, :onshelf_at => 2.days.ago, :created_at => 2.days.ago)
#item2 = Factory(:item, :owner=>#user, :onshelf_at => 1.day.ago, :created_at => 1.day.ago)
end
it "should have the right items in the right order" do
#user.items.should == [#item2, #item1]
end
I checked the console, and found that onshelf_at wasn't listening to the instance initialization of Factory Girl. In its stead, it followed before_create rule, and valued to the time when test was run!
Failure/Error: #user.items.should == [#item2, #item1]
expected: [#<Item id: 2, description: "this is an item", img_link: "http://www.example.com/photos/some_pic.jpg", category_id: 5, onshelf: true, created_at: "2011-11-20 11:19:15", updated_at: "2011-11-21 11:19:15", onshelf_at: "2011-11-21 11:19:15", owner_id: 1>, #<Item id: 1, description: "this is an item", img_link: "http://www.example.com/photos/some_pic.jpg", category_id: 5, onshelf: true, created_at: "2011-11-19 11:19:15", updated_at: "2011-11-21 11:19:15", onshelf_at: "2011-11-21 11:19:15", owner_id: 1>]
got: [#<Item id: 1, description: "this is an item", img_link: "http://www.example.com/photos/some_pic.jpg", category_id: 5, onshelf: true, created_at: "2011-11-19 11:19:15", updated_at: "2011-11-21 11:19:15", onshelf_at: "2011-11-21 11:19:15", owner_id: 1>, #<Item id: 2, description: "this is an item", img_link: "http://www.example.com/photos/some_pic.jpg", category_id: 5, onshelf: true, created_at: "2011-11-20 11:19:15", updated_at: "2011-11-21 11:19:15", onshelf_at: "2011-11-21 11:19:15", owner_id: 1>] (using ==)
How can I fix this?
A quick fix would be to add a condition to your calculate_onshelf_time method:
self.onshelf_at = DateTime.now if self.onshelf_at.nil?
But - you don't even need all this. If you can (that is, if you have control over the schema), replace the onshelf_at attribute with a created_at column, which will automatically be set by Rails at the time of creation. If you're using migrations:
create_table :foo do |t|
# ...
t.timestamps
end
will add created_at and updated_at timestamps to the model.