I am new to testing in rails. I am trying to test the update action for my systems controller in my application using rspec.
Here is the code for the test I am running:
describe 'Put update' do
before (:each) do
#system = FactoryGirl.create(:system, name:"WAM", responsible_personnel: "Jeff", criticality: 1, status: "Available")
end
context "valid attributes" do
it "located the requested #system" do
put :update, id: #system, system: FactoryGirl.attributes_for(:system)
assigns(:contact).should eq(#system)
end
end
end
My factory code:
# spec/factories/systems.rb
require 'faker'
FactoryGirl.define do
factory :system do |f|
f.name { Faker::Lorem.word }
f.responsible_personnel { Faker::Name.name}
f.criticality {Faker::Number.between(1,5)}
f.status "Available" || "Unavailable"
end
factory :invalid_system, parent: :system do |f|
f.name nil
end
end
When I run the test i get this error:
expected: #<System id: 285, name: "WAM", responsible_personnel: "Jeff", created_at: "2016-02-25 18:34:43", updated_at: "2016-02-25 18:34:43", criticality: 1, status: 0>
got: nil
(compared using ==)
Here is my systems controller update action
def update
if #system.update(system_params)
flash[:success] = "System successfully updated."
redirect_to edit_system_path
else
format.html { render :edit }
end
end
Your spec is expecting there to be a variable called #contact created in your controller. I'm guessing that's a typo, and your assigns statement should be:
assigns(:system).should eq(#system)
Related
I am trying to write a simple test to validate updating an Employee and while it works in practice I wanted to write the test anyway.
RSpec.describe EmployeesController, type: :controller do
before(:each) do
admin_user = FactoryBot.create(
:user,
user_type: 1,
email: "admin#admin.com",
password: "oeifhoi2345tf",
password_confirmation: "oeifhoi2345tf"
)
login_as(admin_user)
#employee = create(:employee)
end
it 'expects employee values to update following update' do
p #employee
put :update, params: {
id: #employee[:id],
employee: { name: "New Name" }
}
#employee.reload
p #employee
expect(#employee.name).to eq("New Name")
end
end
The test fails and #employee.name remains unchanged. I have a feeling the update it not even occurring because I added a print line to my controller and I do not see it in my logs:
def update
p "IN UPDATE"
if #employee.update(employee_params)
redirect_to edit_employee_path(#employee[:id])
else
render :edit
end
end
Is there something I am missing in my put call?
I have a carts controller in my app
class CartsController < ApplicationController
def show
#cart = Cart.find(session[:cart_id])
#products = #cart.products
end
end
and wrote test cartscontroller_spec.rb
RSpec.describe CartsController, type: :controller do
describe 'GET #show' do
let(:cart_full_of){ create(:cart_with_products, products_count: 3)}
before do
get :show
end
it { expect(response.status).to eq(200) }
it { expect(response.headers["Content-Type"]).to eql("text/html; charset=utf-8")}
it { is_expected.to render_template :show }
it 'should be products in current cart' do
expect(assigns(:products)).to eq(cart_full_of.products)
end
end
end
My factories.rb looks such:
factory(:cart) do |f|
f.factory(:cart_with_products) do
transient do
products_count 5
end
after(:create) do |cart, evaluator|
create_list(:product, evaluator.products_count, carts: [cart])
end
end
end
factory(:product) do |f|
f.name('__product__')
f.description('__well-description__')
f.price(100500)
end
but I have got an error:
FCartsController GET #show should be products in current cart
Failure/Error: expect(assigns(:products)).to eq(cart_full_of.products)
expected: #<ActiveRecord::Associations::CollectionProxy [#<Product id: 41, name: "MyProduct", description: "Pro...dDescription", price: 111.0, created_at: "2016-11-24 11:18:43", updated_at: "2016-11-24 11:18:43">]>
got: #<ActiveRecord::Associations::CollectionProxy []>
Looks like I have no created products at all because of empty product model array ActiveRecord::Associations::CollectionProxy [], simultaneously, I investigate product`s id is increasing with every test attempt.At the moment I have no solid ideas that is wrong
The id of the created cart is not assigned to the session of your get :show.
before do
session[:cart_id] = cart_full_of.id
get :show
end
# or
before do
get :show, session: { cart_id: cart_full_of.id }
end
UPDATE:
Your find in the controller need the session[:cart_id] value, but your test didn't provide this data to the controller request. If you use one of the codes above the test request provides the session to the 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
I'm using devise + rspec + factory + shoulda and having trouble with my controller specs. I've read a bunch of articles and docs but couldn't figure out what the best way is to log_in the user and use that user instance.
Task is nested under user so index route is /users/:user_id/tasks and task belongs_to :assigner, class_name: "User" and belongs_to :executor, class_name: "User"
At the moment with following code both tests fail. What is the best approach for properly sign_in the user and use it in the controller tests?
The error message for the first one:
Failure/Error: expect(assigns(:tasks)).to eq([assigned_task, executed_task])
expected: [#<Task id: 1, assigner_id: 1, executor_id: 2, .....>, #<Task id: 2, assigner_id: 3, executor_id: 1, ......>]
got: nil
(compared using ==)
The error for the second one:
Failure/Error: it { is_expected.to respond_with :ok }
Expected response to be a 200, but was 302
tasks_controller_spec.rb
require "rails_helper"
describe TasksController do
describe "when user is signed in" do
describe "collections" do
login_user
let(:assigned_task) { create(:task, assigner: #user) }
let(:executed_task) { create(:task, executor: #user) }
let(:other_task) { create(:task) }
context "GET index" do
before do
get :index, user_id: #user.id
end
it "assigns user's tasks" do
expect(assigns(:tasks)).to eq([assigned_task, executed_task])
end
it { is_expected.to respond_with :ok }
end
context "GET incoming_tasks"
end
end
end
controller_macros.rb
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = create(:user)
sign_in #user
end
end
end
tasks controller
def index
#tasks = Task.alltasks(current_user).uncompleted.includes(:executor, :assigner).order("deadline DESC").paginate(page: params[:page], per_page: Task.pagination_per_page)
end
Add following line in rails_helper.
config.include ControllerMacros, :type => :controller
SEE this thread.
I am assuming this only fails in rspec. When you test in browser it works fine.
Here is my controller spec
before do
#order = Order.new
end
it "should call find & assign_attributes & test delivery_start methods" do
Order.should_receive(:find).with("1").and_return(#order)
Order.any_instance.should_receive(:assign_attributes).with({"id"=>"1", "cancel_reason" => "random"}).and_return(#order)
Order.any_instance.should_receive(:delivery_start).and_return(Time.now)
post :cancel, order: {id:1, cancel_reason:"random"}
end
The failure is this:
Failure/Error: Unable to find matching line from backtrace
(#<Order:0x007fdcb03836e8>).delivery_start(any args)
expected: 1 time with any arguments
received: 2 times with any arguments
# this backtrace line is ignored
But I'm not sure why delivery_start is being called twice based on this controller action:
def cancel
#order = Order.find(cancel_params[:id])
#order.assign_attributes(cancel_params)
if (#order.delivery_start - Time.now) > 24.hours
if refund
#order.save
flash[:success] = "Your order has been successfully cancelled & refunded"
redirect_to root_path
else
flash[:danger] = "Sorry we could not process your cancellation, please try again"
render nothing: true
end
else
#order.save
flash[:success] = "Your order has been successfully cancelled"
redirect_to root_path
end
end
I would suggest you test the behavior and not the implementation. While there are cases where you would want to stub out the database doing it in a controller spec is not a great idea since you are testing the integration between your controllers and the model layer.
In addition your test is only really testing how your controller does its job - not that its actually being done.
describe SomeController, type: :controller do
let(:order){ Order.create } # use let not ivars.
describe '#cancel' do
let(:valid_params) do
{ order: {id: '123', cancel_reason: "random"} }
end
context 'when refundable' do
before { post :cancel, params }
it 'cancels the order' do
expect(order.reload.cancel_reason).to eq "random"
# although you should have a model method so you can do this:
# expect(order.cancelled?).to be_truthy
end
it 'redirects and notifies the user' do
expect(response).to redirect_to root_path
expect(flash[:success]).to eq 'Your order has been successfully cancelled & refunded'
end
end
end
end
I would suggest more expectations and returning true or false depending on your use. Consider the following changes
class SomeController < ApplicationController
def cancel
...
if refundable?
...
end
end
private
def refundable?
(#order.delivery_start - Time.now) > 24.hours
end
end
# spec/controllers/some_controller_spec.rb
describe SomeController, type: :controller do
describe '#cancel' do
context 'when refundable' do
it 'cancels and refunds order' do
order = double(:order)
params = order: {id: '123', cancel_reason: "random"}
expect(Order).to receive(:find).with('123').and_return(order)
expect(order).to receive(:assign_attributes).with(params[:order]).and_return(order)
expect(controller).to receive(:refundable?).and_return(true)
expect(controller).to receive(:refund).and_return(true)
expect(order).to receive(:save).and_return(true)
post :cancel, params
expect(response).to redirect_to '/your_root_path'
expect(session[:flash]['flashes']).to eq({'success'=>'Your order has been successfully cancelled & refunded'})
expect(assigns(:order)).to eq order
end
end
end
end
Sorry, this is a very unsatisfactory answer, but I restarted my computer and the spec passed...
One thing that has been a nuisance for me before is that I've forgotten to save the code, i.e., the old version of the code the test is running against called delivery_start twice. But in this case, I definitely checked that I had saved. I have no idea why a restart fixed it...