Rails: Testing named scopes with RSpec - ruby-on-rails

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

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.

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.

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

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.

Related

Build in-test associations using Factory_girl

I want to be able to create associations with ID only in each specific test, trying to avoid defining them in the factory.
I've been following Rails 4 Test Prescriptions
Avoid defining associations automatically in factory_girl definitions.
Set them test by test, as needed. You’ll wind up with more manageable
test data.
class Workspace < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :workspace
end
This is what I want
test "" do
project_with_id = build_stubbed(:project)
workspace_with_id = build_stubbed(:workspace)
workspace_with_id.projects.push(project_with_id)
end
I am using build_stubbed to create valid ID's, which gives the following error:
*** RuntimeError Exception: stubbed models are not allowed to access the database - Project#save({:validate=>true})
So, reading factory girl's documentation I came up with working associations, but I don't want to define them in the factory, not even with traits.
FactoryGirl.define do
factory :project do
association :workspace, strategy: :build_stubbed
end
end
test "" do
project = build_stubbed(:project)
end
This works because I can call project.workspace, and both have a valid ID
How can I create valid associations (with ID), but without touching the database, only using Factory girl to create independent objects?
You could do something like this if you are using Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user_id: user1.id) }
also in Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user: user1) }
In minitest/test_unit I believe
user1 = FactoryGirl.create(:user)
user_social_profile1 = FactoryGirl.create(:user_social_profile, user_id: user1.id)
I am sorry I did not explain the issues associated with using build_stubbed with factory associations, this answer does a really good job at explaining that.
build_stubbed initializes a record and fakes persistence. So what you get is a record that answers true to persisted? and has an faked ID.
Its quite useful but the approach does not work when it comes associations as you would need to stub out large parts of ActiveRecord.
Instead you want to use create:
before do
#project = create(:project)
#workshop = create(:workshop, project: #project)
end
As far as I understand, you don't need an inverse to work, e.g. you'd be satisfied with workspace.projects being empty, wouldn't you?
This should work for you then:
workspace = build_stubbed(:workspace)
project = build_stubbed(:project, workspace: workspace)
If you need workspace.projects, you may use this:
project = build_stubbed(:project)
workspace = build_stubbed(:workspace, projects: [project])
project.workspace = workspace

Rspec mocking for testing queries of an associated record

I have the following model:
class Kueue < ActiveRecord::Base
attr_accessible :name, :user_id
belongs_to :user
has_and_belongs_to_many :photos
scope :empty, includes(:photos).where{ photos.id.eq(nil) }
scope :with_photos, includes(:photos).where{ photos.id.not_eq(nil) }
end
I want to write specs for the scopes, to make sure they're reliably working. My problem is how to deal with the Photo model. Photos have a lot of validations on them, for instance they must belong_to a User. So, I can write a working spec for these queries like so:
describe "queries" do
before :each do
#empty = Fabricate(:kueue)
#full = Kueue.new :name => "Full Queue"
#full.photos << Fabricate(:photo)
#full.save
end
it "should find empty queues" do
Kueue.empty.should_not include(#full)
end
it "should find queues with photos" do
Kueue.with_photos.should_not include(#empty)
end
end
However, as you can imagine this is sloooow. This makes a bunch of calls to the database. (1) to make two kueues, (2) to make a photo, (3) to make a user who owns the photo... and so on, down the line.
This seems like a simple enough problem, all I need is one join record between a photo and a kueue and I can test this really fast. So how would you go about mocking this interaction so you can test it faster and in better isolation?
PS: I'm using Rails 3.2.8, Squeel (hence the query syntax) and my model is called Kueue because Queue is a reserved word in Rails :)
i think that it would not make sense to mock in the context of scopes.
scopes are basically database-queries and you want to make sure they work with, you know, the database.
so if your problem is test-performance, then i would suggest:
try out fixtures in this case
OR
skip model validation, to reduce the models needed

Mongoid + Cucumber

I try to run a scenario with Cucumber, through Capybara, in a Rails 3.2.3 app supported by Mongoid. The aim is to have current user add a book to his collection.
Everything goes ok, but the final step definition, where I check that the amount of books is now one, fails.
But if I check on the app controller, the size actually increased. And actually, when I send reload to the user in the step definition, it passes:
user.reload.books(true).size.should == 1
I'm afraid this behavior could harm my app once in production. Any advice how to make sure all tests and app behaviors are consistent?
UPDATE
I checked the test.log to see what's going on.
Calling reload I get this query to MongoDB:
find({"count"=>"books",
"query"=>{:_id=>{"$in"=>[BSON::ObjectId('4f889b473dffd63235000004')]}},
"fields"=>nil}).limit(-1)
while without the reload I get this:
find({"count"=>"books", "query"=>{:_id=>{"$in"=>[]}}, "fields"=>nil}).limit(-1)
It practically doesn't query against the user if I don't reload the model, which doesn't make much sense to me.
The following works for me (updated with actual Cucumber example)
I built a Rails project to test out your issue, rails 3.2.3, mongoid 2.4.8, mongo 1.6.2, mongodb 2.0.4, cucumber 1.1.9.
The following (association generated methods) work as expected, without need for refresh:
user.books << book
book.users << user
Then I tried to bypass the association, which was what I thought that you were doing.
user.push(:book_ids, book.id)
book.push(:user_ids, user.id)
These DO bypass the association, resulting in incomplete (one-way instead of two-way) references, but the memory and db state is consistent. So my guess in my previous answer about what you were experiencing was wrong, there's no refresh needed and you are probably doing something else. Note that you/we do not want the incomplete references, please do not push directly to the internals for Mongoid referenced relations.
Are you using the association append "<<" for adding a user or a book? My current conclusion is that Mongoid referenced relations work as advertized for my test of your issue. There's no need for refresh.
Here's the model:
class User
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
has_and_belongs_to_many :books
end
class Book
include Mongoid::Document
field :title, type: String
field :author, type: String
has_and_belongs_to_many :users
end
Cucumber feature
Feature: UserAndBook
Test adding a book to a user_s books
Scenario: add_book_to_user
Given starting with no users and no books
And a new user
And that the new user has no books
And a new book
And add book to user
Then I can check that the user has a book
Cucumber steps
require 'test/unit/assertions'
require File.expand_path('../../../test/test_helper', __FILE__)
World(Test::Unit::Assertions)
Given 'starting with no users and no books' do
User.delete_all
Book.delete_all
assert_equal(0, User.count)
assert_equal(0, Book.count)
end
Given 'a new user' do
#user = User.create(first_name: 'Gary', last_name: 'Murakami')
end
Given 'that the new user has no books' do
assert_equal(0, #user.books.count)
end
Given 'a new book' do
#book = Book.create(title: 'A Tale of Two Cities', author: 'Charles Dickens')
end
Given 'add book to user' do
#user.books << #book
end
Then 'I can check that the user has a book' do
assert_equal(1, #user.books.count)
end
I'm open to further exchange of info to help to address your issue.
Blessings,
-Gary
P.S. Looking at the log, it is interesting to see that user.books.length does an actual db "find count $in" query rather than a local array length.
Previous answer
You've pretty much answered your own question. In Rails, you need to use the reload method whenever data for your model has changed in the database, otherwise you will just be looking at the previously loaded/instantiated/cached state of your model. With update of just an attribute, things look pretty consistent, but associations are more complicated and the inconsistency becomes more obvious.

Testing creation of associated objects on callback with Rspec on Rails

Trying to wrap my head around rspec and proper testing and having some hard time to do the following properly
Let's say we have three classes like
Class User
belongs_to :company
has_and_belongs_to_many :roles
end
Class Company
has_many :users
end
Class Role
has_and_belongs_to_many :users
end
In the User class I have before_create callback that assign the user default 'company_admin' role, if the user is first one to be associated with the company
def check_for_initial_company_admin_role
if self.company.users.count == 0
self.roles << Role.find_by_name("company_admin")
end
end
How do I properly test in my model spec that the user gets assigned the 'company_admin' role in case he's the first user associated with the company?
UPDATE
working solution
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
Factory(:role)
user = Factory(:user)
user.roles.count.should be > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
Factory.define :user do |f|
f.email { Factory.next(:email) }
f.password "secret"
f.password_confirmation "secret"
f.association :company
end
Factory.define :role do |f|
f.name "company_admin"
end
Factory.define :company do |f|
f.name "foobar"
f.vat_id "1234"
end
I would approach it like this:
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
company = Factory(:company)
user = Factory(:user)
company.users << user
user.roles.count.should > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
An assumption here that may be incorrect is that you are using Factory Girl in your test framework. If not, that doesn't really change the "meat" of this test...just those first lines where you create the company and user.
You could also optionally check the user from the company side of things but honestly that feels like a different test entirely--one testing the association between those models.
The approach I would take is that since this is actually a model test you need to create and alter a real model objects, rather than mocking out those objects. If this were a controller test, I'd mock the models and stub the models aggressively.
I hope this helps and addresses your question. If not, let me know where I'm off base and I'll make another pass at it :) I'm only about a year into rspec but I've found that once I wrapped my head around how to test models vs. controllers I've come to love it.
Without changing your existing logic, I would test this logic in the user_spec like so:
describe User do
let!(:admin_role) { Role.create!(name: 'company_admin') }
let!(:company) { Company.create! }
it 'should be added to the default role when created' do
user = company.users.create!(name: 'Joe', email: 'joe#email.com')
user.should have(1).roles
user.roles.first.should == admin_role
end
end
Note: I would be using something like FactoryGirl for the admin role and company objects to make them reusable.
Your use of the role name to indicate behavior is not ideal. It will likely lead to lots of scattered logic throughout your application where you are finding the role by its name and checking the name with if/else or case statements. I would recommend using Rail's single table inheritance and moving all the admin role logic to a separate class. It will keep the model's logic cleaner and make testing much easier.

Checking ActiveRecord Associations in RSpec

I am learning how to write test cases using Rspec. I have a simple Post Comments Scaffold where a Post can have many Comments. I am testing this using Rspec. How should i go about checking for Post :has_many :comments. Should I stub Post.comments method and then check this with by returning a mock object of array of comment objects? Is testing for AR associations really required ?
Since ActiveRecord associations should be well-tested by the Rails test suite (and they are), most people don't feel the need to make sure they work -- it's just assumed that they will.
If you want to make sure that your model is using those associations, that's something different, and you're not wrong for wanting to test that. I like to do this using the shoulda gem. It lets you do neat things like this:
describe Post do
it { should have_many(:comments).dependent(:destroy) }
end
Testing associations is good practice generally, especially in an environment where TDD is highly regarded- other developers will often look to your specs before looking at the corresponding code. Testing associations makes sure that your spec file most accurately reflects your code.
Two ways you can test associations:
With FactoryGirl:
expect { FactoryGirl.create(:post).comments }.to_not raise_error
This is a relatively superficial test that will, with a factory like:
factory :post do
title { "Top 10 Reasons why Antelope are Nosy Creatures" }
end
return you a NoMethodError if your model lacks a has_many association with comments.
You can use the ActiveRecord #reflect_on_association method to take a more in-depth look at your association. For instance, with a more complex association:
class Post
has_many :comments, through: :user_comments, source: :commentary
end
You can take a deeper look into your association:
reflection = Post.reflect_on_association(:comment)
reflection.macro.should eq :has_many
reflection.options[:through].should eq :user_comments
reflection.options[:source].should eq :commentary
and test on whatever options or conditions are relevant.
If you'd rather not use an external gem like shoulda to test your associations (see Robert Speicher's Answer for details on that), another option is to use reflect_on_association to get the AssociationReflection object for the relevant association, and then assert on that:
describe Post do
it "should destroy its comments when it is destroyed" do
association = Post.reflect_on_association(:comments)
expect(association).to_not be_nil
expect(association.options[:dependent]).to eq :destroy
end
end
Most people don't test the associations, as Rails already has unit tests to make sure those methods work correctly. If you are doing something complex, like involving a proc or something, you might want to explicitly test it. Usually you can do this by just doing
a = Post.new
a.comments << Comment.new
assert a.save
assert a.comments.size == 1
or something akin to that.

Resources