I am getting the following error when I run rspec. Could really do with some help here! I am not sure if the nested resources or ajax calls are contributing to the rspec failure.
1) GoalsController GET #new renders the :new template
Failure/Error: expect(response).to render_template :new
expecting <"new"> but rendering with <[]>
# ./spec/controllers/goals_controller_spec.rb:7:in `block (3 levels) in <top (required)>'
Here are my codes as shown below.
routes.rb
Rails.application.routes.draw do
resources :strategies, :only => :none do
resources :goals
end
resources :goals, :only => :none do
resources :objectives
end
end
goals_controller.rb
class GoalsController < ApplicationController
respond_to :html, :js
def new
#strategy = Strategy.find(params[:strategy_id])
end
def index
#strategy = Strategy.find(params[:strategy_id])
end
def create
#user = User.find(current_user.id)
#strategy = Strategy.find(params[:strategy_id])
#goal = #strategy.goals.create(goal_params.merge(
start_date: #strategy.start_date,
end_date: #strategy.end_date,
created_by: #user.id))
respond_to do |format|
format.js { }
end
end
private
def goal_params
params.require(:goal).permit(:name, :budget)
end
end
goals_controller_spec.rb
require 'rails_helper'
RSpec.describe GoalsController, type: :controller do
describe 'GET #new' do
it "renders the :new template" do
get :new, strategy_id: 2
expect(response).to render_template :new
end
end
end
Like Sergey Sokolov suggested in the comment, try removing all :only => :none
in the routes.
resources will generate routes for all CRUD actions,:only => :none is basically saying you don't want it to generate at all.
Related
I'm trying to wrap my head about Rspec and Controller tests, more specifically a JSON request. In this case, I'm trying to hit the v1_devices_path route:
routes.rb
v1_device_scans POST /v1/devices/:device_serial_number/scans(.:format) v1/scans#create
v1_device GET /v1/devices/:serial_number(.:format) v1/devices#show
PATCH /v1/devices/:serial_number(.:format) v1/devices#update
PUT /v1/devices/:serial_number(.:format) v1/devices#update
And my controller:
controllers/v1/devices_controller.rb
class V1::DevicesController < ApplicationController
before_action :set_device, only: [:show, :update]
respond_to :json
def show
#device = Device.find_by(serial_number: params[:serial_number])
render :json => #device.as_json
end
def update
if #device.update(device_params)
render json: #device, status: :ok, location: #device
else
render json: #device.errors, status: :unprocessable_entity
end
end
private
def set_device
#device = Device.find_by!(serial_number: params[:serial_number])
end
def device_params
params.require(:device).permit(
:serial_number,
:name,
:diagnostic_checkin_status,
:diagnostic_dns,
:diagnostic_ping,
:assigned_internal_ip,
:assigned_external_ip,
:assigned_gateway_ip,
:version,
:timezone,
:task_id,
:scan_status,
:scan_progress
)
end
end
As of right now my test is super simple... just want to make sure I get some results back:
spec/controllers/v1/devices_controller_spec.rb
require 'rails_helper'
RSpec.describe V1::DevicesController, type: :controller do
it "shows device info" do
device = FactoryGirl.create(:device)
get v1_device_path(device.serial_number), :format => :json
expect(response.status).to be(200)
end
end
After some tweaking, I've gotten to the point where it looks like my url is being created correctly, but I'm still getting the no route matches error:
1) V1::DevicesController shows device info
Failure/Error: get v1_device_path(device.serial_number), :format => :json
ActionController::UrlGenerationError:
No route matches {:action=>"/v1/devices/41442305171c430ab253ba1ad95c5c61", :controller=>"v1/devices", :format=>:json}
# /usr/local/bundle/gems/rails-controller-testing-1.0.2/lib/rails/controller/testing/template_assertions.rb:61:in `process'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:33:in `block in process'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:100:in `catch'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:100:in `_catch_warden'
# /usr/local/bundle/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:33:in `process'
# /usr/local/bundle/gems/rails-controller-testing-1.0.2/lib/rails/controller/testing/integration.rb:12:in `block (2 levels) in <module:Integration>'
# ./spec/controllers/v1/devices_controller_spec.rb:11:in `block (2 levels) in <top (required)>'
What am I doing wrong?
This is another solution to not change type into request. This error shows because of adding serial_number parameter in show devise route, you can add serial_number in your get url.
RSpec.describe V1::DevicesController, type: :controller do
it "shows device info" do
device = FactoryGirl.create(:device)
get :show, params: { serial_number: device.serial_number }, :format => :json
expect(response.status).to be(200)
end
end
I hope this help you.
Alright, starting with #sebastián's comment, I changed the spec to:
require 'rails_helper'
RSpec.describe V1::DevicesController, type: :request do
it "shows device info" do
headers = {
"ACCEPT" => "application/json", # This is what Rails 4 accepts
}
device = FactoryGirl.create(:device)
get v1_device_path(device.serial_number), :headers => headers
expect(response.content_type).to eq("application/json")
end
end
And my test passes now.
post_controller file
class PostsController < ActionController::Base
before_action :authenticate_user!
def index
#post = current_user.posts.paginate(page: params[:page], per_page: 5)
respond_to do |format|
format.html
format.json { render json: #post }
end
end
def new
#post = Post.new
end
def create
#post = current_user.posts.build(post_param)
if #post.save
redirect_to action: 'index'
else
render 'new'
end
post_controller_test
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
include Devise::TestHelpers
def setup
#user = users(:Bob)
#post = Post.new
end #passed
test 'logged in should get show' do
sign_in #user
get :index
assert_response :success
end #passed
test 'not authenticated should get redirect' do
get :index
assert_response :redirect
end #passed
test 'should get index' do
get :index
assert_response :success
assert_not_nil assigns(:posts)
end #failing
test "should destroy post" do
assert_difference('Post.count', -1) do
delete :destroy, id: #post
end
assert_redirected_to posts_path
end #failing
...
devise is setup and working fine but why I am getting 302 error in last two cases. Is it because I am not passing #user parameters to it? I did but it was still throwing the same error. I also checked out my routes file which is fine because post_controller is working fine in development mode.
What I am doing wrong here?
Edit-1
I tried to create test cases for create method
def setup
#user = users(:bob)
#p = posts(:one)
#post = Post.new
end
test 'should create post' do
sign_in #user
assert_difference('Post.count') do
post :create, post: { name: #p.name, value: #p.value}
end
end
I am getting ActionController::ParameterMissing: param is missing or the value is empty: post while in my controller class I do have
params.require(:post).permit(:name, :value, :user_id)
I also have all parameters in my .yml file i.e.
one:
name: 2
value: 3
It looks like you need to sign in before trying the index action. You're also testing the wrong instance variable name. You're testing #posts, but you've defined #post in the controller. Try this test instead:
test 'should get index' do
sign_in #user
get :index
assert_response :success
assert_not_nil assigns(:post)
end
I am writing a rails application with devise and testing in rspec. I have an issue where my rspec fails the user_authenticate when the user is not logged in. All of my specs pass except for the last one- the error it gives is
"Failure/Error: get :show, id: course NoMethodError:undefined method `authenticate' for nil:NilClass"
I suspect I am having this issue because I have a before_action :authenticate_user! call and for someone not logged in, it tries to authenticate nil. Is there a way to make it fail gracefully and redirect to user_session? I tried to create an inherited version of authenticate_user to do the redirect, but it does not appear to work. I know this is probably a noob question but I have extensively searched around without any solution. thanks!
This is my controller:
class CoursesController < ApplicationController
before_action :authenticate_user!, except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
def authenticate_user!
if user_signed_in?
super
else
redirect_to user_session
end
end
end
This is my spec:
require 'rails_helper'
RSpec.describe CoursesController, :type => :controller do
describe "user access " do
before(:each) do
#user = create(:user)
#request.env['devise.mapping'] = Devise.mappings[:user]
sign_in :user, #user
end
describe 'GET #index' do
it 'renders the :index view' do
get :index
expect(response).to render_template :index
end
end
describe 'GET #show' do
it 'assigns the requested course to #course' do
course = create(:course)
get :show, id: course
expect(assigns(:course)).to eq course
end
it 'renders the :show template' do
course = create(:course)
get :show, id: course
expect(response).to render_template :show
end
end
end
describe "guest access " do
describe 'GET #index' do
it 'renders the :index view' do
get :index
expect(response).to render_template :index
end
end
describe 'GET #show' do
it 'redirects to the login url' do
course = create(:course)
get :show, id: course
expect(response).to redirect_to 'user_session'
end
end
end
end
It seems that devise does the redirect to "users#sessions" itself when you add :authenticate_user! to the show action for a guest or a user that is not signed in.
Try removing your custom :authenticate_user! method and add "only: [:show]" to your before_action
class CoursesController < ApplicationController
before_action :authenticate_user!, only: [:show], except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
end
Update
class CoursesController < ApplicationController
before_action :authenticate_user!, except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
if user_signed_in?
render :show
else
redirect_to user_session
end
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
end
This isn't a super satisfying result but it appears as if authenticate_user! does not properly work with rspec. When I load the page directly, it correctly redirects to the login page, I am still interested to know what the proper work around is.
Can I have some sort of OR statement where I can first check if a user exists? There must be a standard way to deal with this problem so I can ensure my app is properly redirecting.
Hi everyone I'm testing my app controllers and I have a problem. I have tests for update action which fails:
describe "PUT #update" do
before :each do
#car_service = create(:car_service)
end
it "locates the requested #message" do
put :update, id: #car_service, car_addition: attributes_for(:car_service)
assigns(:car_addition).should eq(#car_service)
end
context "valid attributes" do
it "changes #car_service's attributes" do
put :update, id: #car_service, car_addition: attributes_for(:car_service, name: "Test")
#car_service.reload
#car_service.name.should eq("Test")
end
it "redirects to the updated message" do
put :update, id: #car_service, car_addition: attributes_for(:car_service)
should redirect_to admin_car_additions_url
end
end
context "invalid attributes" do
it "does not change #car_addition's attributes" do
put :update, id: #car_service, car_addition: attributes_for(:car_service, name: nil)
#car_service.reload
#car_service.name.should_not be_nil
end
it "re-renders the edit method" do
put :update, id: #car_service, car_addition: attributes_for(:car_addition)
should render_template :edit
end
end
end
when i run this tests only one test not pass("re-renders the edit method") and throw out following error:
Failure/Error: should render_template('edit')
expecting <"edit"> but rendering with <[]>
# ./spec/controllers/admin/car_additions_controller_spec.rb:100:in `block (4 levels) in <top (required)>
My controller looks like this:
module Admin
class CarAdditionsController < ApplicationController
include Admin::BaseController
load_and_authorize_resource
add_breadcrumb I18n.t('car_additions.car_addition.home'), :admin_root_path
add_breadcrumb I18n.t('car_additions.car_additions'), :admin_car_additions_path
def index
end
def new
add_breadcrumb t('car_additions.car_addition.new')
end
def edit
add_breadcrumb t('car_additions.car_addition.edit')
end
def create
if #car_addition.save
flash[:notice] = t("car_additions.created")
redirect_to action: :index
else
add_breadcrumb t('car_additions.car_addition.new')
render :new
end
end
def update
if #car_addition.update(car_addition_params)
flash[:notice] = t("car_additions.updated")
redirect_to action: :index
else
render :edit
end
end
def destroy
#car_additon.destroy
flash[:error] = t("car_additions.destroy")
redirect_to action: :index
end
private
def car_addition_params
params.require(:car_addition).permit(:name, :type, :image,
:image_cache, :remove_image)
end
end
end
I'm using devise and CanCan for authorization. Please help.
I'm pass attributes_for(:car_addition) because this is not valid attributes. When I changed this to:
attributes_for(:car_addition, name: nil) it's still not working...
You should use render_views method in order to have your views rendered in specs:
describe "PUT #update" do
render_views
# ...
end
I'm making an app where people can create jobs, but can only destroy the jobs they've created. I know that my app lets people destroy jobs because I can test it manually, but RSpec is lagging behind.
Here's the relevant test:
jobs_controller_spec.rb
require 'spec_helper'
describe JobsController do
let!(:user) { FactoryGirl.create(:user) }
let!(:job) { FactoryGirl.create(:job, user: user) }
let!(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
let!(:wrong_job) { FactoryGirl.create(:job, user: wrong_user) }
[...]
describe "correct user control" do
before { sign_in user }
describe "users can only delete their own jobs" do
it "should not change job count" do
expect do
delete :destroy, id: wrong_job.id
end.to_not change(Job, :count)
end
end
describe "users can delete their own jobs" do
it "should decrease job count" do
expect do
delete :destroy, id: job.id
end.to change(Job, :count).by(-1)
end
end
end
end
Here's the failing test:
1) JobsController correct user control users can delete their own jobs should decrease job count
Failure/Error: expect do
count should have been changed by -1, but was changed by 0
# ./spec/controllers/jobs_controller_spec.rb:41:in `block (4 levels) in <top (required)>'
jobs_controller.rb
class JobsController < ApplicationController
skip_before_action :require_signin, only: [:index, :show]
skip_before_action :correct_user, only: [:index, :show, :new, :create]
before_action :set_job, only: [:show, :edit, :update, :destroy]
[...]
def destroy
#job.destroy
respond_to do |format|
format.html { redirect_to jobs_url }
format.json { head :no_content }
end
end
private
def set_job
#job = Job.find(params[:id])
end
def job_params
params.require(:job).permit(:title, :org, :internship, :postdate, :filldate, :location, :link, :description)
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_filter :require_signin
before_filter :correct_user
include SessionsHelper
private
def require_signin
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#job = current_user.jobs.find_by(id: params[:id])
redirect_to root_url if #job.nil?
end
end
rake routes
Prefix Verb URI Pattern Controller#Action
jobs GET /jobs(.:format) jobs#index
POST /jobs(.:format) jobs#create
new_job GET /jobs/new(.:format) jobs#new
edit_job GET /jobs/:id/edit(.:format) jobs#edit
job GET /jobs/:id(.:format) jobs#show
PATCH /jobs/:id(.:format) jobs#update
PUT /jobs/:id(.:format) jobs#update
DELETE /jobs/:id(.:format) jobs#destroy
[...]
As Peter Alfvin points out, if there is authentication in controller specs you have to setup a session and pass it as third parameter, for example:
...
let(:some_user) { User.create }
def valid_session
{ user_id: some_user.id }
end
describe "DELETE destroy" do
it "destroys the requested job" do
job = Job.create! valid_attributes
expect {
delete :destroy, { :id => job.to_param }, valid_session
}.to change(Job, :count).by(-1)
end
end
The solution is to pass no_capybara: true to sign_in. Capybara doesn't work with controller tests, so one can't use capybara to manage the sign in process.
Thanks to Patrick Brinich-Langlois for the solution.