Can I have some feedback with rspec when writing controllers specs? - ruby-on-rails

I was wondering if i could have some feedbacks with the controller spec bellow. In fact i'm new when writing specs and controller's spec are way different from model's spec ! So i'm wondering if i may not go in the wrong direction...
subjects_controller.rb
def show
#subject = Subject.find(params[:id])
if #subject.trusted?(current_user)
#messages = #subject.messages
else
#messages = #subject.messages.public
#messages = #messages + #subject.messages.where(:user_ids => current_user.id)
#messages.uniq!
end
# sort the list
#messages = #messages.sort_by(&:created_at).reverse
if !#subject.company.id == current_user.company.id
redirect_to(subjects_path, :notice => "Invalid subject")
end
end
subjects_controller_spec.rb
require 'spec_helper'
describe SubjectsController do
before(:each) do
#subject = mock_model(Subject)
end
context "for signed users" do
before(:each) do
#current_user = sign_in Factory(:user)
end
context "GET #show" do
before(:each) do
Subject.stub!(:find, #subject).and_return(#subject)
end
context "when current_user is trusted" do
before(:each) do
messages = []
company = mock_model(Company)
#subject.should_receive(:trusted?).and_return(true)
#subject.should_receive(:messages).and_return(messages)
#subject.should_receive(:company).and_return(company)
end
it "should render success" do
get :show, :id => #subject
response.should be_success
end
end
context "when current_user is not trusted" do
before(:each) do
messages = []
company = mock_model(Company)
#subject.should_receive(:trusted?).and_return(false)
#subject.should_receive(:messages).and_return(messages)
messages.should_receive(:public).and_return(messages)
#subject.should_receive(:messages).and_return(messages)
messages.should_receive(:where).and_return(messages)
#subject.should_receive(:company).and_return(company)
end
it "should render success" do
get :show, :id => #subject
response.should be_success
end
end
context "when subject's company is not equal to current_user's company" do
# I have no idea of how to implement ==
end
end
end
end
Factories.rb
Factory.define :user do |u|
u.first_name 'Test User' #
u.username 'Test User' #
u.surname 'TheTest' #
u.email 'foo#foobar.com' #
u.password 'please' #
u.confirmed_at Time.now #
end

As far as I can tell you're on the right path. The basic idea is to completely isolate your controller code from model and view in these tests. You appear to be doing that--stubbing and mocking model interaction.

Don't write RSpec controller specs at all. Use Cucumber stories instead. Much easier, and you get better coverage.

Related

Test controller method. Rspec

I want to test the controller method, but I can not find the example of testing method with order and search .
This is my controller:
class Admin::HotelsController < Admin::BaseController
helper_method :sort_column, :sort_direction
def index
#hotels = Hotel.search(params[:search], params[:search_column]).order(sort_column + ' ' + sort_direction)
end
def show
#hotel = Hotel.find(params[:id])
end
def update
#hotel = Hotel.find(params[:id])
if #hotel.update_attributes(hotel_params)
redirect_to admin_hotels_path
else
render(:edit)
end
end
private
def hotel_params
params.require(:hotel).permit(:title, :description, :user_id, :avatar, :price, :breakfast, :status, address_attributes: [:state, :country, :city, :street])
end
def sort_column
Hotel.column_names.include?(params[:sort]) ? params[:sort] : 'created_at'
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
end
end
This is test for this controller.
require 'rails_helper'
describe Admin::HotelsController do
login_admin
describe 'GET index' do
it 'render a list of hotels' do
hotel1, hotel2 = create(:hotel), create(:hotel)
get :index
expect(assigns(:hotels)).to match_array([hotel1, hotel2])
end
end
describe 'GET show' do
it 'should show hotel' do
#hotel = create(:hotel)
get :show, { id: #hotel.to_param, template: 'hotels/show' }
expect(response).to render_template :show
end
end
end
I don't know how testing index method. Please help or give me a link with information about this. Thanks!
If it may help you, I personally prefer to have minimals tests for the controllers for various reasons:
1) as I was beginning in rails testing I read many articles saying it's a good idea
2) it allows you to tests in isolation model methods:
describe 'GET index' do
it 'render a list of hotels' do
hotel1, hotel2 = create(:hotel), create(:hotel)
get :index
expect(assigns(:hotels)).to match_array([hotel1, hotel2])
end
end
here your test matches the result of your query on the model. You can split it like this:
describe 'GET index' do
it 'render a list of hotels' do
hotel1, hotel2 = create(:hotel), create(:hotel)
Hotel.should_receive(:search).with(YOUR PARAMS)
get :index
response.response_code.should == 200
end
end
and then test the result of Hotel.search in a model test.
3) it allows you to test the feature and not some random things that are not really relevant:
describe 'GET show' do
it 'should show hotel' do
#hotel = create(:hotel)
get :show, { id: #hotel.to_param, template: 'hotels/show' }
expect(response).to render_template :show
end
end
here "expect(response).to render_template :show" seems like testing that rails rendering system is properly working. I assume that's not what you want to test, you may prefer (that's what I would do):
describe 'GET show' do
it 'should show hotel' do
#hotel = create(:hotel)
Hotel.should_receive(:find).with(YOUR PARAMS)
get :show, { id: #hotel.to_param, template: 'hotels/show' }
response.response_code.should == 200
end
end
and then test what is supposed to appear on the web page with a feature test using something like capybara gem unless you're rendering some json: in this case match the json values in the controller.
By the way: "#hotel = create(:hotel)" the # is not necessary here as you're in the "it". Moreover you can create such entry like this:
context "" do
before(:each) do
#hotel = create(:hotel) # here the # is necessary for the variable to be
end # accessible in the it
it "" do
end
end
or even like this:
context "" do
let(:hotel) { create(:hotel) } # you can call it in the test by using hotel and it
it "" do # will be insert in you db only when it's in the "it"
end # if you want it to be created in the "it" without
end # calling hotel for nothing, use let!
I would suggest using
describe 'GET index' do
let(:hotel1) { create(:hotel) }
let(:hotel2) { create(:hotel) }
it 'render index template' do
get :index
expect(response).to render_template :index
end
it 'render asc ordered hotels' do
get :index
# if you are using json responses
json = JSON.parse(response.body)
expect(json['hotels'].first).to eq hotel1
expect(json['hotels'].last ).to eq hotel2
# or any similar approach to get test the hotels in response
end
it 'render desc ordered hotels' do
get :index, {direction: 'desc'}
# if you are using json responses
json = JSON.parse(response.body)
expect(json['hotels'].first).to eq hotel2
expect(json['hotels'].last ).to eq hotel1
# or any similar approach to get test the hotels in response
end
# you can complete these tests yourself
it 'render hotels sorted with different_column_than_created_at asc'
it 'render hotels sorted with different_column_than_created_at desc'
end

How to create an article of the user

I am testing the controllers with RSpec, FactoryGirls.
It is my factories.rb
FactoryGirl.define do
factory :user do |user|
user.sequence(:name) { Faker::Internet.user_name }
user.email Faker::Internet.email
user.password "password"
user.password_confirmation "password"
end
factory :article do
user
title Faker::Lorem.sentence(5)
content Faker::Lorem.paragraph(20)
end
end
How can i create an article of the user here
And this is articles_controller_spec
describe ArticlesController do
let(:user) do
user = FactoryGirl.create(:user)
user.confirm!
user
end
describe "GET #index" do
it "populates an array of articles of the user" do
#how can i create an article of the user here
sign_in user
get :index
assigns(:articles).should eq([article])
end
it "renders the :index view" do
get :index
response.should render_template :index
end
end
end
The older version, instead of traits, is this:
describe ArticlesController do
..
describe "GET #index" do
it "populates an array of articles of the user" do
article = FactoryGirl.create(:article, :user => user)
sign_in user
get :index
assigns(:articles).should eq([article])
end
..
end
describe ArticlesController do
let(:user) do
user = FactoryGirl.create(:user)
user.confirm!
user
end
describe "GET #index" do
it "populates an array of articles of the user" do
#how can i create an article of the user here
sign_in user
get :index
assigns(:articles).should eq([article])
end
it "renders the :index view" do
get :index
response.should render_template :index
end
it "assign all atricles to #atricles" do
get :index
assigns(:atricles).your_awesome_test_check # assigns(:articles) would give you access to instance variable
end
end
end
you can specify an User factory with articles already
FactoryGirl.define do
factory :user do |user|
user.sequence(:name) { Faker::Internet.user_name }
user.email Faker::Internet.email
user.password "password"
user.password_confirmation "password"
end
factory :article do
user
title Faker::Lorem.sentence(5)
content Faker::Lorem.paragraph(20)
end
trait :with_articles do
after :create do |user|
FactoryGirl.create_list :article, 2, :user => user
end
end
end
then in your controller test
FactoryGirl.create :user, :with_articles # => returns user with 2 articles
UPDATE
i think you want to see all articles per user.. if thats the case use
get :index, {:id => user.id}
that way you look for the user and get all articles in your controller
#user = User.find(params[:id]);
#articles = #user.articles
if thats not the case then just doing
#articles = Article.all
after using the trait :with_articles should display at least 2 Articles
you can test this with a simply asserting like
expect(#article.size).to eq(2)

RSpec controller spec passes without corresponding instance variable in controller

I wrote a spec to test the instance variable #vendors in the index action of my vendors controller. If I remove #vendors from the vendors controller the spec still passes. Any ideas as to why assigns(:vendors) would pass if #vendors doesn't exists in the controller. Heres my code:
Vendors Controller
class VendorsController < ApplicationController
load_and_authorize_resource
def index
# #vendors = Vendor.all
end
end
Vendors Controller Spec
require 'spec_helper'
require 'ruby-debug'
describe VendorsController do
login_user
before(:each) do
#vendor = Factory(:vendor)
end
describe "GET index" do
before(:each) do
#ability.can :read, Vendor
end
it "assigns all vendors to #vendors" do
get :index
assigns(:vendors).should == [#vendor]
end
it "should render the index template" do
get :index
response.should be_success
response.code.should eq("200")
response.should render_template("index")
end
end
end
Vendors Factory
FactoryGirl.define do
factory :vendor do |f|
f.sequence(:name) { |n| "Test#{n}" }
f.sequence(:address) { |n| "000 Test#{n} drive, Hampton" }
f.state "Virginia"
f.zip "00000"
f.sequence(:telephone) { |n| "000-000-000#{n}" }
f.sequence(:poc) { |n| "Test#{n}" }
end
end
Thanks
Because load_and_authorize_resource actually loads and authorizes.
So your code is unnecessary.
You could change with authorize_resource, thus the spec will fail.

Object.save failed in spec data validation

Here is the failed spec code for create in customer controller:
describe CustomersController do
before(:each) do
#the following recognizes that there is a before filter without execution of it.
controller.should_receive(:require_signin)
controller.should_receive(:require_employee)
end
render_views
describe "'create' successful" do
before(:each) do
category = Factory(:category)
sales = Factory(:user)
#customer = Factory.attributes_for(:customer, :category1_id => category.id, :sales_id => sales.id)
session[:sales] = true
session[:user_id] = sales.id
session[:user_name] = sales.name
session[:page_step] = 1
session['page1'] = customers_path
end
it "should create one customer record" do
lambda do
post 'create', #customer
end.should change(Customer, :count).by(1)
end
it "should redirect to customers path" do
put 'create', #customer
flash[:notice].should_not be_nil
response.should redirect_to(customers_path)
end
end
end
The customer has both sales id and category id which belong to user and category table respectively.
Here is the spec failure error:
1) CustomersController GET customer page 'create' successful should create one customer record
Failure/Error: lambda do
count should have been changed by 1, but was changed by 0
# ./spec/controllers/customers_controller_spec.rb:37:in `block (4 levels) in <top (required)>'
2) CustomersController GET customer page 'create' successful should redirect to customers path
Failure/Error: flash[:notice].should_not be_nil
expected: not nil
got: nil
# ./spec/controllers/customers_controller_spec.rb:44:in `block (4 levels) in <top (required)>'
Here is the app code for create in customer controller:
def create
if session[:sales]
#customer = Customer.new(params[:customer], :as => :roles_new_update)
#customer.sales_id = session[:user_id]
if #customer.save
#message = "New customer #{params[:name]} was created. Please check it out"
#subject = "New customer #{params[:name]} was created BY {#session[:user_name]}"
UserMailer.notify_tl_dh_ch_ceo(#message, #subject, session[:user_id])
redirect_to session[('page' + session[:page_step].to_s).to_sym], :notice => 'Customer was created successfaully!'
else
render 'new', :notice => 'Customer was not saved!'
end
end
end
Here is the code in factories.rb:
Factory.define :customer do |c|
c.name "test customer"
c.short_name "test"
c.email "t#acom.com"
c.phone "12345678"
c.cell "1234567890"
c.active 1
c.category1_id 2
c.sales_id 1
c.address "1276 S. Highland Ave, Lombard, IL 67034"
c.contact "Jun C"
end
Factory.define :category do |c|
c.name "category name"
c.description "test category"
c.active true
end
Factory.define :user do |user|
user.name "Test User"
user.email "test#test.com"
user.password "password1"
user.password_confirmation "password1"
user.status "active"
user.user_type "employee"
end
It seems that the error was caused by #customer.save returning false and the code for "if #customer.save" was not executed. So the problem may be with the #customer generated by Factory which seems good to me. The code is executed without any problem when saving a customer.
Any suggestions? Thanks.
I would break this up into two specific tests. Right now you're unsure of two things:
is the customer is being told to save itself?
Is there a validation that is preventing customer from being saved?
The quickest path is to change #customer.save to #customer.save! and see if there are any exceptions raised (it will do so if a validation failed).
I recommend you split this up though. To test #1, in the controller spec:
it "should tell the customer to save itself when there is a session[:sales]" do
session[:sales] = true
customer_mock = double(:customer)
customer_mock.should_receive(:sales_id=)
customer_mock.should_receive(:save).and_return(:true)
Customer.stub(:new => cutomer_mock)
post 'create'
end
Then in your customer_spec, test out:
it "should be valid with factory specs" do
customer = Customer.new(Factory.attributes_for(:customer))
customer.should be_valid
end
post :create, :customer => #customer
solves the problem with above.

Force controller to use current_user with mocking

I am trying to specify in my RSpec tests that my controller should use current_user.projects.find() instead of Project.find() I am using the Mocha mocking framework and was trying something like this:
controller.current_user.projects.expects(:find).returns(#project)
I have already mocked out controller.stubs(:current_user).returns(#profile)
This test passes with this even when I use the Project.find() implementation. How can I test that my controller is calling off of the correct object?
Edit (adding additional code):
I have Projects and Tasks, Project have many tasks. This is the show method for displaying a task in a project that is owned by current_user
Action in the controller:
def show
#project = current_user.projects.find_by_id(params[:cardset_id])
if #project.nil?
flash[:notice] = "That project doesn't exist. Try again."
redirect_to(projects_path)
else
#task = #project.tasks.find_by_id(params[:id])
end
end
This is the test that is not checking that the cardsets method was called off the current_user object.
Current Test:
context "with get to show" do
context "with valid project" do
before(:each) do
#project = Factory(:project)
#task = Factory(:task)
#profile = #project.profile
ApplicationController.stubs(:require_user).returns(true)
controller.stubs(:current_user).returns(#profile)
Project.stubs(:find_by_id).returns(#project)
#project.tasks.stubs(:find_by_id).returns(#task)
get :show, :project_id => #project.id, :id => #task.id
end
it "should assign task" do
assigns[:task].should_not be_nil
end
it "should assign project" do
assigns[:project].should_not be_nil
end
end
context "with invalid project" do
before(:each) do
Project.stubs(:find_by_id).returns(nil)
get :show, :project_id => #project.id, :id => #task.id
end
it "should set flash" do
flash[:notice].should match(/doesn't exist/i)
end
it "should redirect" do
response.should redirect_to(cardsets_url)
end
end
end
Based on the little you've told us, I think you need:
#profile.expects(:find).returns(#project)

Resources