Reputation: 7210
I am trying to test my default scope line in a model.
My test is as follows:
it 'orders by ascending name by default' do
expect(Coaster.scoped.to_sql).to eq Coaster.order(:name).to_sql
end
My error is:
expected: "SELECT \"coasters\".* FROM \"coasters\" ORDER BY name ASC, name"
got: "SELECT \"coasters\".* FROM \"coasters\" ORDER BY name ASC"
What does the , name
part at the end of the first line of the error mean and how can I resolve this?
UPDATE:
My test:
describe 'default scope' do
let!(:coaster_one) { FactoryGirl.create(:coaster, name: "Tower of Terror") }
let!(:coaster_two) { FactoryGirl.create(:coaster, name: "Apocalypse") }
it 'orders by ascending name' do
Coaster.all.should eq [:coaster_two, :coaster_one]
end
end
My errors:
expected: [:coaster_two, :coaster_one]
got: [#<Coaster id: 5, name: "Apocalypse", height: "60", speed: 60.0, length: "160", inversions: 4, material: nil, notes: nil, lat: nil, lng: nil, manufacturer_id: nil, park_id: 408, created_at: "2013-07-23 20:48:52", updated_at: "2013-07-23 20:48:52", slug: "apocalypse-at-alton-towers", style: nil, covering: nil, ride_style: nil, model: nil, layout: nil, order: nil, dates_ridden: nil, times_ridden: nil>, #<Coaster id: 4, name: "Tower of Terror", height: "60", speed: 60.0, length: "160", inversions: 4, material: nil, notes: nil, lat: nil, lng: nil, manufacturer_id: nil, park_id: 407, created_at: "2013-07-23 20:48:52", updated_at: "2013-07-23 20:48:52", slug: "tower-of-terror-at-alton-towers", style: nil, covering: nil, ride_style: nil, model: nil, layout: nil, order: nil, dates_ridden: nil, times_ridden: nil>]
(compared using ==)
UPDATE 2:
It looks as though Rails 4 deprecates the use of default_scope so in light of this, I have remove the default_scope from my model and replaced it with a standard scope.
The new scope is now:
scope :by_name_asc, lambda { order("name ASC") }
and my associated test is:
describe 'scopes' do
let!(:coaster_one) { FactoryGirl.create(:coaster, name: "Tower of Terror") }
let!(:coaster_two) { FactoryGirl.create(:coaster, name: "Apocalypse") }
it "orders coasters by ascending name" do
Coaster.by_name_asc.should eq [:coaster_two, :coaster_one]
end
end
When running this test I get:
1) Coaster scopes orders coasters by ascending name
Failure/Error: Coaster.by_name_asc.should eq [:coaster_two, :coaster_one]
expected: [:coaster_two, :coaster_one]
got: [#<Coaster id: 15, name: "Apocalypse", height: "60", speed: 60.0, length: "160", inversions: 4, material: nil, notes: nil, lat: nil, lng: nil, manufacturer_id: nil, park_id: 528, created_at: "2013-07-24 22:36:50", updated_at: "2013-07-24 22:36:50", slug: "apocalypse-at-alton-towers", style: nil, covering: nil, ride_style: nil, model: nil, layout: nil, order: nil, dates_ridden: nil, times_ridden: nil>, #<Coaster id: 14, name: "Tower of Terror", height: "60", speed: 60.0, length: "160", inversions: 4, material: nil, notes: nil, lat: nil, lng: nil, manufacturer_id: nil, park_id: 527, created_at: "2013-07-24 22:36:50", updated_at: "2013-07-24 22:36:50", slug: "tower-of-terror-at-alton-towers", style: nil, covering: nil, ride_style: nil, model: nil, layout: nil, order: nil, dates_ridden: nil, times_ridden: nil>]
(compared using ==)
Diff:
@@ -1,2 +1,2 @@
-[:coaster_two, :coaster_one]
+[#<Coaster id: 15, name: "Apocalypse", height: "60", speed: 60.0, length: "160", inversions: 4, material: nil, notes: nil, lat: nil, lng: nil, manufacturer_id: nil, park_id: 528, created_at: "2013-07-24 22:36:50", updated_at: "2013-07-24 22:36:50", slug: "apocalypse-at-alton-towers", style: nil, covering: nil, ride_style: nil, model: nil, layout: nil, order: nil, dates_ridden: nil, times_ridden: nil>, #<Coaster id: 14, name: "Tower of Terror", height: "60", speed: 60.0, length: "160", inversions: 4, material: nil, notes: nil, lat: nil, lng: nil, manufacturer_id: nil, park_id: 527, created_at: "2013-07-24 22:36:50", updated_at: "2013-07-24 22:36:50", slug: "tower-of-terror-at-alton-towers", style: nil, covering: nil, ride_style: nil, model: nil, layout: nil, order: nil, dates_ridden: nil, times_ridden: nil>]
# ./spec/models/coaster_spec.rb:10:in `block (3 levels) in <top (required)>'
Any ideas on what is going wrong?
Upvotes: 15
Views: 9538
Reputation: 153
You may get some deprecation warnings about Mohamad's answer, to resolve, just use expect
and to
it 'orders by ascending name' do
expect(Book.all).to eq [book_two, book_one]
end
Upvotes: 0
Reputation: 35349
A better way to test your default scope is to use real data with an expected output. Create dummy objects for your class, then query the class and compare the result to what you expect to be the correct order:
describe 'default scope' do
let!(:book_one) { Book.create(name: "The Count of Monte Cristo") }
let!(:book_two) { Book.create(name: "Animal Farm") }
it 'orders by ascending name' do
Book.all.should eq [book_two, book_one]
end
end
let!(:book_one)
creates an instance of Book
and assigns it to a local variable called book_one
. Its name
attribute is The Count of Monte Cristo. let!(:book_two)
does something similar.
If the default order is name ASC
, then querying the Book
model should return an ActiveRecord relation with book_two
as the first element ("Animal..."), and book_one
as the second element ("The C...").
We can test this expectation as follows:
Book.all.should eq [book_two, book_one]
This does not test your SQL, but it tests the direct output of your code, which is more useful. It is also database independent.
Upvotes: 20
Reputation: 34327
We often try to test scopes with factories and real values. When we just want to confirm that the scope is setup correctly, we do the following:
describe '.sorted' do
let(:nodes) { described_class.sorted.order_values }
let(:columns) { nodes.map { |n| n.instance_variable_get('@expr').name } }
it do
expect(columns)
.to eq [:service_type, :mri_type, :ct_slices, :mri_magnet_strength,
:created_at]
end
it { expect(nodes.map(&:direction)).to eq [:asc, :asc, :asc, :asc, :asc] }
end
This is a test for:
scope :sorted, (
lambda do
order(:service_type, :mri_type, :ct_slices, :mri_magnet_strength,
:created_at)
end
)
Upvotes: 0
Reputation: 47471
Most likely, you'll have more than one model with a similar default scope (if not, mostly ignore this answer) so you can put this Rspec example into a shared_example where you can call it from a variety of model specs.
My preferred method of checking a default scope is to make sure the default ActiveRecord::Relation
has the expected clause (order
or where
or whatever the case may be), like so:
spec/support/shared_examples/default_scope_examples.rb
shared_examples_for 'a default scope ordered by name' do
it 'adds an ordered by name clause' do
described_class.scoped.order_clauses.should include("name")
end
end
And then in your Coaster
spec (and any other spec that has the same default scope), all you need to is:
spec/models/coaster_spec.rb
describe Coaster
it_behaves_like 'a default scope ordered by name'
# other spec examples #
end
Finally, you can see how this pattern can be extended to all sorts of default scopes and keeps things clean, organized and, best of all, DRY.
Upvotes: 3
Reputation: 226
about the last update, it should be:
Coaster.all.should eq [coaster_two, coaster_one]
instead of:
Coaster.all.should eq [:coaster_two, :coaster_one]
Upvotes: 0
Reputation: 27779
Your example is adding a second order term to the default, so you're getting name ASC, name
. Try stripping off the first order clause first with reorder('')
, e.g.:
expect(Coaster.scoped.to_sql).to eq Coaster.reorder('').order('name ASC').to_sql
Upvotes: 3