rctneil
rctneil

Reputation: 7210

Testing Default scope in RSpec

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

Answers (6)

JK Gunnink
JK Gunnink

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

Mohamad
Mohamad

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

Dan Kohn
Dan Kohn

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

Joshua Pinter
Joshua Pinter

Reputation: 47471

DRY It Up, Use Shared Examples

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

Beelphegor
Beelphegor

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

Mori
Mori

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

Related Questions