RSpec, stubbing nested resource methods - ruby-on-rails

I've got two models:
class Solution < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :foreign_key => :user_id
end
class User < ActiveRecord::Base
has_many :solutions
end
and I nest solutions within users like this:
ActionController::Routing::Routes.draw do |map|
map.resources :users, :has_many => :solutions
end
and finally here's the action I"m trying to spec:
class SolutionsController < ApplicationController
before_filter :load_user
def show
if(#user)
#solution = #user.solutions.find(params[:id])
else
#solution = Solution.find(params[:id])
end
end
private
def load_user
#user = User.find(params[:user_id]) unless params[:user_id].nil?
end
end
My question is, how the heck do I Spec #user.solutions.find(params[:id])?
Here's my current spec:
describe SolutionsController do
before(:each) do
#user = Factory.create(:user)
#solution = Factory.create(:solution)
end
describe "GET Show," do
before(:each) do
Solution.stub!(:find).with(#solution.id.to_s).and_return(#solution)
User.stub!(:find).with(#user.id.to_s).and_return(#user)
end
context "when looking at a solution through a user's profile" do
it "should find the specified solution" do
Solution.should_receive(:find).with(#solution.id.to_s).and_return(#solution)
get :show, :user_id => #user.id, :id => #solution.id
end
end
end
But that gets me the following error:
1)Spec::Mocks::MockExpectationError in 'SolutionsController GET Show, when looking at a solution through a user's profile should find the specified solution'
<Solution(id: integer, title: string, created_at: datetime, updated_at: datetime, software_file_name: string, software_content_type: string, software_file_size: string, language: string, price: string, software_updated_at: datetime, description: text, user_id: integer) (class)> received :find with unexpected arguments
expected: ("6")
got: ("6", {:group=>nil, :having=>nil, :limit=>nil, :offset=>nil, :joins=>nil, :include=>nil, :select=>nil, :readonly=>nil, :conditions=>"\"solutions\".user_id = 34"})
Can anybody help me with how I can stub #user.solutions.new(params[:id])?

Looks like I found my own answer, but I'm going to post it up here since I can't seem to find a whole lot about this on the net.
RSpec has a method called stub_chain: http://apidock.com/rspec/Spec/Mocks/Methods/stub_chain
which makes it easy to stub a method like:
#solution = #user.solutions.find(params[:id])
by doing this:
#user.stub_chain(:solutions, :find).with(#solution.id.to_s).and_return(#solution)
So then I can write an RSpec test like this:
it "should find the specified solution" do
#user.solutions.should_receive(:find).with(#solution.id.to_s).and_return(#solution)
get :show, :user_id => #user.id, :id => #solution.id
end
And my spec passes. However, I'm still learning here, so if anybody thinks my solution here is no good, please feel free to comment this and I try to get it totally right.
Joe

With the new RSpec syntax, you stub a chain like so
allow(#user).to receive_message_chain(:solutions, :find)
# or
allow_any_instance_of(User).to receive_message_chain(:solutions, :find)

Related

How best to test controller variable assignment of has_many associations?

I am trying and failing to test a controller for variable assignment of the belongs_to objects. These are controller tests and there are a number of areas I could really appreciate with some help on, namely
Should I be writing such tests here and in this way.
If so how could i get it working.
Code as below:
Company.rb
class Company < ApplicationRecord
belongs_to :user
has_many :employees, inverse_of: :company
has_many :quotes, inverse_of: :company
end
Quote.rb
class Quote < ApplicationRecord
belongs_to :company
end
Employee.rb
class Employee < ApplicationRecord
belongs_to :company
end
Company has a controller with usual CRUDs, Quote has a controller with Show and Index, Employee does not have a controller. Companies#create creates all three objects and redirect_to's to Quotes#show which renders various attrs from all three models.
companies_controller.rb #create
def create
#company = current_user.companies.new(company_params)
if #company.save
#quote = #company.quotes.last
#employees = #company.employees.all
redirect_to company_quote_url(#company, #quote, #employees), notice: 'Quote request created'
else
render :new
end
end
quotess_controller.rb #show
def show
#company = Company.find(params[:company_id])
#quote = #company.quotes.find(params[:id])
#employees = #company.employees.all
end
I have a Factory Girl factory set up for eahc of the models:
Companies.rb
FactoryGirl.define do
factory :company do
sequence(:co_name) { |n| "Acme Co #{n}" }
co_number "06488522"
postcode "al1 1aa"
industry :financial_services
factory :company2 do
end
factory :company3 do
end
end
end
Quotes.rb
FactoryGirl.define do
factory :quote do
lives_overseas true
payment_frequency :monthly
factory :quote2 do
end
factory :quote3 do
end
end
end
Employees.rb
FactoryGirl.define do
factory :employee1, class: Employee do
first_name "MyString"
last_name "MyString"
email "test#test.com"
gender "MyString"
date_of_birth "2000-06-20"
salary 10000
factory :employee2 do
end
factory :employee3 do
end
end
end
And I am trying to write controller tests for Quote#show and to test the assignment of the three objects, i.e.; #company, #quote & #employees to the relataive variables. Code so far as below:
quotes_controller_spec.rb
require 'rails_helper'
RSpec.describe QuotesController, type: :controller do
let(:user) {FactoryGirl.create(:user) }
let(:company) { FactoryGirl.create(:company, user: user) }
let(:employee1) { FactoryGirl.create(:employee1, company: company) }
let(:employee2) { FactoryGirl.create(:employee2, company: company) }
let(:employee3) { FactoryGirl.create(:employee3, company: company) }
let(:quote) { FactoryGirl.create(:quote, company: company) }
describe "GET #show" do
it "returns http success" do
get :show, params: { company_id: company.id, id: quote.id, , employee_id: employee1.id }
expect(response).to have_http_status(:success)
end
it "assigns requested quote to #quote" do
get :show, params: { company_id: company.id, id: quote.id, employee1.id: employee1.id } #, employee_id: employee1.id
expect(assigns(:quote)).to eq(quote) # passes fine
expect(assigns(:company)).to eq(company) # passes fine
expect(assigns(:employee1)).to eq(employee1) # fails
end
end
end
I get an error as below:
Failures:
1) QuotesController GET #show assigns requested quote to #quote
Failure/Error: expect(assigns(:employee1)).to eq(employee1)
expected: #<Employee id: 1, first_name: "MyString", last_name: "MyString", email: "test#test.com", gender: "m",...alary: 10000, company_id: 178, created_at: "2017-07-01 11:21:27", updated_at: "2017-07-01 11:21:27">
got: nil
(compared using ==)
# ./spec/controllers/quotes_controller_spec.rb:28:in `block (3 levels) in <top (required)>'
When i run the app and use params.inspect in Quote#show template after a Company#create these are the params that are passed:
<ActionController::Parameters {"controller"=>"quotes", "action"=>"show", "company_id"=>"109", "id"=>"109", "format"=>"#<Employee::ActiveRecord_AssociationRelation:0x007fc2694a07f8>"} permitted: false>
I feel like there are a few core things I am not getting right here;
I need somehow to declare the associations within Factory Girl
My tests should somehow be testing the presence of a collection and its assignment to the #employees variable in Quotes#show, not assignment of just one employee record, which is what I'm trying, and failing, to do above.
I am unsure about whether I am crossing 'lines of separation' that perhaps ought to be present because I am testing on other model objects (Company, Quote and Employee) created in Companies#create and rendered in Quotes#show.
Any help and or guidance appreciated. The afternoon reading and googling leaves me still at a loss as to how I can get my testing strategy right here and the syntax correct for it to work properly. Incidentally all works in the app just fine, I'd just like to be able to test the assignment of the correct object in this Quotes#show method. Thanks.
The answer to part 2 of this question, re. testing assignment of a collection, in the context of my code was either:
expect(assigns(:employees)).to include(employee1)
or
expect(assigns(:employees)).to eq([employee1])
Feedback on parts 1 and 3 of this question still sought.
Thanks

RSpec: Controller spec with polymorphic resource, "No route matches" error

I'm learning RSpec by writing specs for an existing project. I'm having trouble with a controller spec for a polymorphic resource Notes. Virtually any other model can have a relationship with Notes like this: has_many :notes, as: :noteable
In addition, the app is multi-tenant, where each Account can have many Users. Each Account is accessed by :slug instead of :id in the URL. So my mulit-tenant, polymorphic routing looks like this:
# config/routes.rb
...
scope ':slug', module: 'accounts' do
...
resources :customers do
resources :notes
end
resources :products do
resources :notes
end
end
This results in routes like this for the :new action
new_customer_note GET /:slug/customers/:customer_id/notes/new(.:format) accounts/notes#new
new_product_note GET /:slug/products/:product_id/notes/new(.:format) accounts/notes#new
Now on to the testing problem. First, here's an example of how I test other non-polymorphic controllers, like invitations_controller:
# from spec/controllers/accounts/invitation_controller_spec.rb
require 'rails_helper'
describe Accounts::InvitationsController do
describe 'creating and sending invitation' do
before :each do
#owner = create(:user)
sign_in #owner
#account = create(:account, owner: #owner)
end
describe 'GET #new' do
it "assigns a new Invitation to #invitation" do
get :new, slug: #account.slug
expect(assigns(:invitation)).to be_a_new(Invitation)
end
end
...
end
When i try to use a similar approach to test the polymorphic notes_controller, I get confused :-)
# from spec/controllers/accounts/notes_controller_spec.rb
require 'rails_helper'
describe Accounts::NotesController do
before :each do
#owner = create(:user)
sign_in #owner
#account = create(:account, owner: #owner)
#noteable = create(:customer, account: #account)
end
describe 'GET #new' do
it 'assigns a new note to #note for the noteable object' do
get :new, slug: #account.slug, noteable: #noteable # no idea how to fix this :-)
expect(:note).to be_a_new(:note)
end
end
end
Here I'm just creating a Customer as #noteable in the before block, but it could just as well have been a Product. When I run rspec, I get this error:
No route matches {:action=>"new", :controller=>"accounts/notes", :noteable=>"1", :slug=>"nicolaswisozk"}
I see what the problem is, but i just can't figure out how to address the dynamic parts of the URL, like /products/ or /customers/.
Any help is appreciated :-)
UPDATE:
Changed the get :new line as requested below to
get :new, slug: #account.slug, customer_id: #noteable
and this causes the error
Failure/Error: expect(:note).to be_a_new(:note)
TypeError:
class or module required
# ./spec/controllers/accounts/notes_controller_spec.rb:16:in `block (3 levels) in <top (required)>'
Line 16 in the spec is:
expect(:note).to be_a_new(:note)
Could this be because the :new action in my notes_controller.rb is not just a #note = Note.new, but is initializing a new Note on a #noteable, like this?:
def new
#noteable = find_noteable
#note = #noteable.notes.new
end
Well the problem here should be that in this line
get :new, slug: #account.slug, noteable: #noteable
you are passing :noteable in params. But, you need to pass all the dynamic parts of the url instead to help rails match the routes. Here you need to pass :customer_id in params. Like this,
get :new, slug: #account.slug, customer_id: #noteable.id
Please let me know if this helps.

RSpec - unable to get all associate attributes in controller

Hello Programmers & Developers!!!, I'm a beginner in RoR and creating a simple project in rails to learn its working, so in that project I'm facing a problem in writing a spec for the create method of controller. When I'm trying to pass the associate attributes of the object in spec file, in controller it isn't get all the attributes.
In the create method of subjects_controller.rb file.I've created a variable called attr in this variable I'm storing all the values sent from subjects_controller_spec.rb file.
attr=(params.require(:subject).permit(:name)).merge(:classroom_ids=>params[:subject][:classroom_ids],:school_ids=>params[:subject][:school_ids])
Now, If I print the value of the attr using p attr in console it's output is the exact output that I want, which is
{"name"=>"Computer", "classroom_ids"=>["1", "2"], "school_ids"=>["1"]}
But, now I'm doing #subject = Subject.new(attr) and printing value of #subject gives the following output
#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>
and after running the test I'm getting my test failed and then I printed the error p #subject.errors it gave me the below output
#<ActiveModel::Errors:0x007fc35444a218 #base=#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>, #messages={:school_ids=>["is not a number"], :classroom_ids=>["is not a number"]}>
So, here is my actual question is why #subject in subjects_controller.rb is not having values of classroom_ids and school_ids? If any solution or suggestion is there then please help me to sort out this problem.
Below I'm providing you all the necessary details to understand the actual problem.
Ruby Version 2.2.4
Rails Version 4.2.0
Database MySQL
Model file subject.rb
class Subject < ActiveRecord::Base
has_and_belongs_to_many :schools
has_and_belongs_to_many :teachers
has_and_belongs_to_many :classrooms
has_and_belongs_to_many :students
validates_presence_of :name, :school_ids, :classroom_ids
validates_numericality_of :school_ids, :classroom_ids
end
Controller file subjects_controller_spec.rb
require 'rails_helper'
RSpec.describe SubjectsController, type: :controller do
before(:each) do
#school1 = FactoryGirl.create(:school)
#classroom1 = FactoryGirl.create(:classroom, :school_id=>#school1.id)
#classroom2 = FactoryGirl.create(:classroom, :school_id=>#school1.id)
#subject = FactoryGirl.build(:subject)
#subject.classrooms<<#classroom1
#subject.classrooms<<#classroom2
#subject.schools<<#school1
end
context "POST create" do
it "should be success" do
# p #subject
# p #subject.classrooms
# p #subject.classroom_ids
attributes=#subject.attributes.merge(:classroom_ids=>#subject.classroom_ids,:school_ids=>#subject.school_ids)
# In below line, I'm sending all the values to the controller to create a new subject.
post :create, :subject=>attributes
response.status.should eq 201
end
end
end
Controller file subjects_controller.rb
class SubjectsController < ApplicationController
before_action :set_subject, only: [:show, :edit, :update, :destroy]
# GET /subjects
def index
#subjects = Subject.all
end
# GET /subjects/1
def show
end
# GET /subjects/new
def new
#subject = Subject.new
end
# GET /subjects/1/edit
def edit
end
# POST /subjects
def create
attr=(params.require(:subject).permit(:name)).merge(:classroom_ids=>params[:subject][:classroom_ids],:school_ids=>params[:subject][:school_ids])
p attr ### here it prints all the values which I want to create subject.###
#subject = Subject.new(attr)
p #subject ### here is the actual problem, It's not printing all the values that need to create a new subject.###
if #subject.save
redirect_to #subject, notice: 'Subject was successfully created.', status: :created
else
p #subject.errors
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /subjects/1
def update
if #subject.update(subject_params)
redirect_to #subject, notice: 'Subject was successfully updated.', status: :ok
else
render :edit, :status => :unprocessable_entity
end
end
# DELETE /subjects/1
def destroy
#subject.destroy
redirect_to subjects_url, notice: 'Subject was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_subject
#subject = Subject.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def subject_params
params.require(:subject).permit(:name, :school_ids, :classroom_ids)
end
end
Factory file subjects.rb
FactoryGirl.define do
factory :subject do
name "Computer"
end
end
RSpec Test Report
rspec spec/controllers/subjects_controller_spec.rb
{"name"=>"Computer", "classroom_ids"=>["1", "2"], "school_ids"=>["1"]}
#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>
#<ActiveModel::Errors:0x007fcdfe8f1a28 #base=#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>, #messages={:school_ids=>["is not a number"], :classroom_ids=>["is not a number"]}>
F
Failures:
1) SubjectsController POST create should be success
Failure/Error: response.status.should eq 201
expected: 201
got: 422
(compared using ==)
# ./spec/controllers/subjects_controller_spec.rb:21:in `block (3 levels) in <top (required)>'
Deprecation Warnings:
Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }` instead. Called from /Users/vishal/project/school_system/spec/controllers/subjects_controller_spec.rb:21:in `block (3 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations to
identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the
deprecation warnings into errors, giving you the full backtrace.
1 deprecation warning total
Finished in 0.40113 seconds (files took 3.03 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/subjects_controller_spec.rb:14 # SubjectsController POST create should be success
Coverage report generated for RSpec to /Users/vishal/project/school_system/coverage. 49 / 332 LOC (14.76%) covered.
For more details you can refer this Github link.
Thanks For Help In Advance.
[ "1", "2" ] is not array of integer but String! #subject has classroom_ids and school_ids, but params always treat input values as String, so validation error occurs in your Subject model. So try below to transform String to Integer:
params[:subject][:classroom_ids].map(&:to_i)
params[:subject][:school_ids].map(&:to_i)
How about this?
restore below without map method in the controller:
params[:subject][:classroom_ids]
params[:subject][:school_ids]
In my PC, by modifing the Subject model as below from your github link and passed the test.
Could you try this?
Class Subject < ActiveRecord::Base
has_and_belongs_to_many :schools
has_and_belongs_to_many :teachers
has_and_belongs_to_many :classrooms
has_and_belongs_to_many :students
validates_presence_of :name, :school_ids, :classroom_ids
validate :validate_classroom_ids
validate :validate_school_ids
private
def validate_classroom_ids
if classroom_ids.any?{ |id| !id.is_a?(Integer) }
errors.add(:classroom_ids, 'is not a number')
return false
end
end
def validate_school_ids
if school_ids.any?{ |id| !id.is_a?(Integer) }
errors.add(:school_ids, 'is not a number')
return false
end
end
end

devise with rspec test fails due to prevoius tests

i have used devise in rspec testing. this is my test
describe BooksController do
before(:all) do
#user = FactoryGirl.create(:user)
end
describe "GET index" do
it "shows list of current user books" do
sign_in #user
book = #user.books.create!(:title => "user")
get :index, {}
assigns(:books).should eq(#user.books)
end
end
describe "GET show" do
it "assigns the requested book as #book" do
sign_in #user
book = #user.books.create!(:title => "user")
visit_count = book.visits.to_i
get :show, {:id => book.to_param}
assigns(:book).should eq(book)
book = Book.find(book.id)
visit_count.should_not eq(book.visits)
end
end
describe "GET new" do
it "assigns a new book as #book" do
sign_in #user
get :new, {}
assigns(:book).should be_a_new(Book)
end
end
end
factory
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "foo#{n}#example.com" }
password '12345678'
password_confirmation '12345678'
confirmed_at Time.now
end
end
book controller
class BooksController < ApplicationController
before_action :authenticate_user!, only: [:index, :edit, :update, :destroy, :new, :my_books, :add_wish_list]
# GET /books
# GET /books.json
def index
#books = current_user.books
end
# GET /books/1
# GET /books/1.json
def show
#book = Book.find(params[:id])
#book.book_visit_count
if(session["warden.user.user.key"].present?)
#book.book_visit_user(session["warden.user.user.key"][0][0])
end
end
# GET /books/new
def new
#book = Book.new
end
end
error
Failure/Error: assigns(:book).should be_a_new(Book)
expected nil to be a new Book(id: integer, title: string, author: string, isbn_10: string, isbn_13: string, edition: integer, print: integer, publication_year: integer, publication_month: string, condition: string, value: integer, status: boolean, stage: integer, description: text, visits: integer, user_id: integer, prefered_place: string, prefered_time: string, created_at: datetime, updated_at: datetime, rating: integer, image: string, publisher: string, goodreads_id: string)
# ./spec/controllers/books_controller_spec.rb:66:in `block (3 levels) in <top (required)>'
the problem is the third test "get new" fails when i run the test as a whole but passes when i run it individually. and also if i remove the before_authenticate! in controller then all test passes.
Again if i commented out the "assigns" in first two describe blocks then all tests pass again.
i am using rails 4.0.2 and rspec 2.14.7 , devise 3.2.2
The only thing I can figure is that your authenticate_user method is failing for users that have previously been authenticated. It's not affecting show because you don't have :show listed in your before_action. You could test this theory by requiring authentication for show as well and seeing if your second example starts failing for before(:all) as well.

Associations with Factory Girl in Controller specs

I'm new to RSpec, and trying to get my head around using Factory Girl with associations in controller specs. The difficulty is:
it's necessary to use "attributes_for" in functional tests
attributes_for "elides any associations"
So if I have models like this:
class Brand < ActiveRecord::Base
belongs_to :org
validates :org, :presence => true
end
class Org < ActiveRecord::Base
has_many :brands
end
And a factory like this:
FactoryGirl.define do
factory :brand do
association :org
end
end
This controller spec fails:
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post :create, brand: attributes_for(:brand)
}.to change(Brand, :count).by(1)
end
end
end
(And if I comment out "validates :org, :presence => true" it passes)
There are a number of solutions suggested and I think I have been making simple errors which have meant that I have not been able to get any of them to work.
1) Changing the factory to org_id per a suggestion on this page failed a number of tests with "Validation failed: Org can't be blank"
FactoryGirl.define do
factory :brand do
org_id 1002
end
end
2) Using "symbolize_keys" looks promising. Here and here it is suggested to use code like this:
(FactoryGirl.build :position).attributes.symbolize_keys
I'm not sure how to apply this in my case. Below is a guess that doesn't work (giving the error No route matches {:controller=>"brands", :action=>"{:id=>nil, :name=>\"MyString\", :org_id=>1052, :include_in_menu=>false, :created_at=>nil, :updated_at=>nil}"}):
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post build(:brand).attributes.symbolize_keys
}.to change(Brand, :count).by(1)
end
end
end
Update
I almost got this working with Shioyama's answer below but got the error message:
Failure/Error: post :create, brand: build(:brand).attributes.symbolize_keys
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: id, created_at, updated_at
So following this question I changed it to:
post :create, brand: build(:brand).attributes.symbolize_keys.reject { |key, value| !Brand.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Which worked!
In your solution 2), you have not passed an action to post which is why it is throwing an error.
Try replacing the code in that expect block to:
post :create, brand: build(:brand).attributes.symbolize_keys

Resources