Rails: Testing named scopes with RSpec

34,902

Solution 1

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.

Solution 2

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.

Solution 3

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
Share:
34,902
andrykonchin
Author by

andrykonchin

Updated on May 10, 2021

Comments

  • andrykonchin
    andrykonchin almost 3 years

    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

  • jaydel
    jaydel almost 13 years
    Are you sure that querying in unnecessary in a model spec? I can understand not doing it in a controller or view spec--mock & stub it away. But I would think that you want to test the model and its relationship to the database here.
  • Jan Minárik
    Jan Minárik almost 13 years
    IMO it is unnecessary when testing named scopes. You should have plenty of other tests that actually read from the database.
  • Rob Davis
    Rob Davis almost 13 years
    Scopes are one area where it certainly makes sense to query the database to fetch real objects. I favor mocking where it's appropriate, but only the simplest scopes will work with mocks. Once you start joining or grouping, they'll break down, and I've made it a rule to test scopes against a real backing store. Configuration-based matchers like the second and third examples are error-prone, IMHO. That is, they're very likely to have the same errors as your code.
  • Chuck Vose
    Chuck Vose over 12 years
    Models get factories, controllers get mocks. In the controller we care about our statuses and responses, in models we care about data. It's true that we shouldn't test baked in things like find, but a scope seems perfectly a perfectly rational thing to test.
  • Sam Peacey
    Sam Peacey almost 12 years
    He mentions nothing about scopes in that post however, why do you lump them in with associations? Scopes can be far more complicated and error prone than associations, and I believe should be tested. Since you invoked the mighty creator, here he suggests that if a scope falls under the umbrella of behaviour (and if it doesn't, why is it there?), then it should be specified.
  • Joost Baaij
    Joost Baaij almost 12 years
    I am not disagreeing that scopes shouldn't be tested. They definitely should. Just not directly; the testing of scopes should be driven by testing the behaviour of the app. I see scopes as similar to associations, since both can have complex options like joins, included models, where/group by/having etcetera. In any case, this is what makes sense to me.
  • steve
    steve about 3 years
    I think when you have logic that builds scopes, particularly if that logic becomes complex, it can be useful to write unit tests around it.