andrykonchin
andrykonchin

Reputation: 2517

Rails: Testing named scopes with RSpec

I am new to testing Rails web applications and RSpec. I work with legacy code and need to add tests. So what is the best way to test finders and named scopes with RSpec?

I find in Google a few approaches but they are not ideal. For example:

http://paulsturgess.co.uk/articles/show/93-using-rspec-to-test-a-named_scope-in-ruby-on-rails

it "excludes users that are not active" do
    @user = Factory(:user, :active => false)
    User.active.should_not include(@user)
end

or

http://h1labs.com/notebook/2008/8/21/testing-named-scope-with-rspec

it "should have a published named scope that returns ..." do
  Post.published.proxy_options.should == {:conditions => {:published => true}}
end

I find best approach (IMHO) in "Rail Test Prescriptions":

should_match_find_method :active_only { :active == true }

where should_match_find_method custom helper method

Upvotes: 44

Views: 42276

Answers (5)

Aleks Tkachenko
Aleks Tkachenko

Reputation: 752

DON"T USE NAMED SCOPES unless IT's NEEDED. One can test named scope with 0 calls to database. But one got to STOP using names scopes and use just class methods. Then its easy to test your app without testing non-relevant APIs. So here is an example of what I like to see in the codebase, look at this code. It asserts that #where got called with expected parameter. We don't need to go deeper and test ActiveRecord ORM or use expensive stuff like creating records and then getting them as a result of the query.

   describe ".with_expired_membership" do
      before do
        allow(described_class).to receive(:where)
      end

      it "returns the expired membership views" do
        described_class.with_expired_membership
        expect(described_class).to have_received(:where).with(
          "(DATE(certified_at) < ? OR date(certified_at) IS NULL)",
          1.year.ago.to_date,
        )
      end
    end

Reason to not use names scopes lays in the complexity to stub objects and where chain to avoid interaction with the database.

Upvotes: 0

notapatch
notapatch

Reputation: 7173

David Chelimsky testing scopes (updated)

David Chelimsky example, (linked by Sam Peacey's comment), modernised.

# app/models/user.rb

class User < ActiveRecord::Base
  scope :admins, -> { where(admin: true) }
end
# spec/models/user_spec.rb

RSpec.describe User, type: :model do
  describe ".admins" do
    it "includes users with admin flag" do
      admin = User.create!(admin: true)
      expect(User.admins).to include(admin)
    end

    it "excludes users without admin flag" do
      non_admin = User.create(admin: false)
      expect(User.admins).not_to include(non_admin)
    end
  end
end

This produces a more 'spec-like' output (when using --format documentation):

User
  .admins
    includes users with admin flag
    excludes users without admin flag

Note about origination of this answer:

David Chelimsky, the RSpec lead at the time, answered this question and Sam Peacey's link to it has more votes than the actual answer. The answer is not easy to find and follow as he is replying to someone and editing their answer in an email chain. This answer cleans that up and updates the RSpec code as, I guess, he would have written it today.

Upvotes: 25

hakunin
hakunin

Reputation: 4231

From https://coderwall.com/p/hc8ofa/testing-rails-model-default_scope-with-rspec

  • no database queries
  • no need to represent the query in a structure

Example:

class Trip < ActiveRecord::Base
  default_scope { order(departure: :asc) }
  ...
end

RSpec.describe Trip, type: :model do
  it "applies a default scope to collections by departure ascending" do
    expect(Trip.all.to_sql).to eq Trip.all.order(departure: :asc).to_sql
  end
end

Upvotes: 5

Joost Baaij
Joost Baaij

Reputation: 7598

The creator of RSpec has recently blogged he thinks Validations are behavior, associations are structure. In other words he finds that associations (and scopes) should not nessesarily be tested directly. Tests for these will follow from the behavior you want.

In other words, current wisdom is that there is no need to test each scope directly, since you will cover these associations by testing the behavior of your application.

Upvotes: 39

Jan Min&#225;rik
Jan Min&#225;rik

Reputation: 3267

The problem with the first approach is that it actually queries the database. It is slow and unnecessary. If you don't mind, you can safely use the first approach. The second approach is fast and clear so I would recommend it.

Upvotes: -2

Related Questions