I'm using Rspec and Rails 3 for testing. I've tested my models and helpers but I'm lost on how to begin testing controllers. Almost all of my data in my controller actions is pulled using something like these examples:
#services = current_account.services
#projects = current_person.projects
#projects = current_account.projects.active
# this is really #projects = current_person.account.projects.active)
I can't seem to find any examples of how to test data that's pulled this way. All of the examples I've found aren't scoped to an account or person. Can anyone point me to an article on how to mock or stub this type of arrangement? Is this a sign that this entire approach isn't correct? Below, I've included an entire sample controller.
Any help would be greatly appreciated.
Thanks,
David
class ServicesController < ApplicationController
# Run authorizations
filter_resource_access
# Respond to ...
respond_to :html, :xml, :json
respond_to :js, :only => [:new, :create, :edit, :update, :destroy]
# GET /services
# GET /services.xml
def index
#services = current_account.services.order("name").paginate(:page => params[:page])
respond_with(#services)
end
# GET /services/1
# GET /services/1.xml
def show
#service = current_account.services.find(params[:id])
respond_with(#service)
end
# GET /services/new
# GET /services/new.xml
def new
#service = current_account.services.new
respond_with(#service)
end
# GET /services/1/edit
def edit
#service = current_account.services.find(params[:id])
respond_with(#service)
end
# POST /services
# POST /services.xml
def create
#service = current_account.services.new(params[:service])
if #service.save
# flash[:notice] = 'A service was successfully created.'
end
respond_with(#service, :location => services_url)
end
# PUT /services/1
# PUT /services/1.xml
def update
#service = current_account.services.find(params[:id])
if #service.update_attributes(params[:service])
# flash[:notice] = 'The service was successfully updated.'
end
respond_with(#service, :location => services_url)
end
# DELETE /services/1
# DELETE /services/1.xml
def destroy
#service = current_account.services.find(params[:id])
if #service.destroy
flash[:notice] = "The service was successfully deleted."
else
flash[:warning] = #service.errors.full_messages.inject("") { |acc, message| acc += message }
end
respond_with(#service)
end
end
–––––– UPDATE
Thanks to Xaid's solution I was able to get a solution:
context "logged_in" do
before(:each) do
#current_account = Factory.create(:account)
controller.stub!(:current_account).and_return(#current_account)
#services = FactoryGirl.create_list(:service, 10, :account => #current_account)
#services << #current_account.services.first
#current_account.services.stub!(:all).and_return(#services)
end
# INDEX
describe "GET services" do
before(:each) do
get :index
end
it "should set #services when accessing GET /index" do
assigns[:services].should == #services
end
it "should respond with success" do
response.should be_success
end
end
end
Can't you use something like this to test your 'index' action
describe "GET 'index'" do
before(:each) do
#user = FactoryGirl.create(:user)
controller.stub!(:current_user).and_return(#user)
#services = FactoryGirl.create_list(:service, 10, :user => #user)
#user.services.stub!(:all).and_return(#services)
end
it "should return a list of services" do
get :index
assigns(:services).should == #services
end
end
If I understood your question correctly, you should be able to stub current_user.services(or projects) and make it return some known value (generated by FactoryGirl in my example) and check that against the value thats stored in your action (for example, #services in your 'index' action).
Related
I'd like to ask why i'm always getting nil value when running rspec controller test ?
I already read in this site and most of answers because plurals word using inside assigns
but in my case thats not working and i still got the same value
This is my Controller
class ContactsController < ApplicationController
load_and_authorize_resource
before_action :find_contact, only: [:show,:edit,:update,:destroy]
def index
authorize! :index, Contact
#contact = Contact.accessible_by(current_ability)
# #contact = Contact.all
end
def show
end
def new
#contact = Contact.new
end
def edit
end
def create
#contact = current_user.contact.new(contact_params)
if #contact.save
redirect_to #contact
else
render 'new'
end
end
def update
# #contact = Contact.find(params[:id])
if #contact.update(contact_params)
redirect_to #contact
else
render 'edit'
end
end
def destroy
#contact.destroy
redirect_to contacts_path
end
private
def contact_params
params.require(:contact).permit(:firstname,
:lastname,
:alamat,
details_attributes: [:id, :number, :_destroy])
end
def find_contact
#contact = Contact.find(params[:id])
end
end
And this is my simple controller test
require 'rails_helper'
RSpec.describe ContactsController do
describe "Contact" do
it "succesfully create the contact" do
contact = FactoryGirl.create(:contact)
get :index
# byebug
expect(assigns(:contacts)).to eq([contact])
end
end
end
Even i change assigns(:contacts) to assigns(:contact) i still got the same value. So where is that i am do wrong ?
Please kindly answer this, big thanks
Even i change assigns(:contacts) to assigns(:contact) i still got the
same value. So where is that i am do wrong ?
assigns and assert_template have been remove and extracted to a gem in Rails 5.
Source
You have an authorization check
authorize! :index, Contact
before the assignment to #contact.
But your test has no setup in order to grant permissions to the requesting user in any way.
It probably makes sense to have an additional test alongside the one you show in order to spot errors like this. E.g:
it "returns 200 (OK)" do
get :index
expect(response.response_code).to eq(200)
end
I am testing a controller which has actions rendering views in format .js.erb .
The tests on these actions raise the following error :
Minitest::UnexpectedError: ActionController::InvalidCrossOriginRequest: Security warning: an embedded tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.
The forgery protection causes this error, as when I put an exception on these actions its ok, but I dont want to disable it.
Here is my controller :
class TasksController < ApplicationController
def new
# TODO add blah later
#task = Task.new
render 'tasks/show_form.js.erb'
end
def create
# FIXME there is bug here
#task = Task.new(task_params)
#task.user = current_user
authorize! :create, #task
save_task
end
def edit
#task = Task.find(params[:id])
authorize! :edit, #task
render 'tasks/show_form.js.erb'
end
def update
#task = Task.find(params[:id])
#task.assign_attributes(task_params)
authorize! :update, #task
save_task
end
def destroy
#task = Task.find(params[:id])
authorize! :destroy, #task
#task.destroy
#tasks = Task.accessible_by(current_ability)
end
private
def save_task
if #task.save
#tasks = Task.accessible_by(current_ability)
render 'tasks/hide_form.js.erb'
else
render 'tasks/show_form.js.erb'
end
end
def task_params
params.require(:task).permit(:title, :note, :completed)
end
end
Here is my test for this controller :
require 'test_helper'
class TasksControllerTest < ActionDispatch::IntegrationTest
#include Devise::Test::ControllerHelpers
def setup
#task = tasks(:one)
#password = "password"
#confirmed_user = User.create(email: "#{rand(50000)}#example.com",
password: #password,
confirmed_at: "2020-02-01 11:35:56")
#unconfirmed_user = User.create(email: "#{rand(50000)}#example.com",
password: #password,
confirmed_at: "")
sign_in(user: #confirmed_user, password: #password)
end
test "should get new" do
get new_task_url
assert_response :success
end
test "should create task" do
assert_difference('Task.count') do
post tasks_url, params: {task: {title: 'Dont fail test !'}}
end
assert_redirected_to task_url(Task.last)
end
test "should get edit" do
get edit_task_url(#task)
assert_response :success
end
test "should update task" do
patch task_url(#task), params: {task: {title: 'updated'}}
assert_redirected_to task_url(#task)
article.reload
assert_equal "updated", article.title
end
test "should destroy task" do
assert_difference('Task.count', -1) do
delete task_url(#task)
end
assert_redirected_to tasks_url
end
end
Do any of you has an idea on how to correct this error ?
Thank you in advance
You might try changing your get request to use the same mechanism that the browser would for an AJAX request, a XMLHttpRequest. You can do this in rails testing with xhr
In your case:
xhr :get, new_task_url
This means you aren't bypassing rails defaults just to get your test to pass.
The solution was to put at the beginning of my controller
skip_before_action :verify_authenticity_token, :only => [:edit,
:new]
Hope this can help someone
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 don't know what's wrong, I tried to add "create" method to my application, and what i get is :
"ActiveRecord::RecordNotFound in UsersController#show"
"Couldn't find User with id=create"
And then code
# Use callbacks to share a common setup
def set_user
#user = User.find(params[:id])
end
# Permit only specific parameters
here's my User controller
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
#users = User.all
end
def show
end
def create
#user = User.new(user_params)
respond_to do |user|
if #user.save(user_params)
user.html { redirect_to users_path, :notice => "User has been created!" }
else
user.html { redirect_to create_user_path(#user), :notice => "Sorry, couldn't create user. Try again!" }
end
end
end
def edit
end
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to users_path, :notice => "User has been updated!" }
else
format.html { redirect_to edit_user_path(#user), :notice => "Sorry, couldn't update user. Try again!" }
end
end
end
def destroy
#user.destroy
respond_to do |d|
d.html { redirect_to users_path, :notice => "User has been successfully destroyed :C !" }
end
end
private
# Use callbacks to share a common setup
def set_user
#user = User.find(params[:id])
end
# Permit only specific parameters
def user_params
params.require(:user).permit(:name, :email)
end
end
The thing is, the index page works perfectly fine, but if i try to go somewhere else, like /users/create i get that error.. I tried changing routes, rewriting the code, nothing helps.My routes are like this:
# Root '/'
root "users#index"
# Show Users
get "users/:id" => "users#show"
Can you guys help me ? I am literally stuck, as to how fix that problem :c
This should help you - you're missing resources :users, which creates a set of RESTful routes for your controller. This, combined with the other answers should help
You don't go to the create action via a URL. It's there to create a new user coming back from the new.html.erb file.
If you want to create a new user you could use /users/new and add a new method to your controller along the lines of:
def new
#user = User.new
end
You will also need to change your routes.rb file to add all the default actions like:
resources :users
I suggest you work through http://guides.rubyonrails.org/getting_started.html.
In your controller:
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
#users = User.all
end
def show
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to users_path, :notice => "User has been created!"
else
render :new
end
end
def edit
end
def update
if #user.update(user_params)
redirect_to users_path, :notice => "User has been updated!"
else
render :edit
end
end
def destroy
#user.destroy
redirect_to users_path, :notice => "User has been successfully destroyed :C !" }
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :email)
end
end
You are using all RESTful actions, so you can add to routes.rb:
resources :users
And remove this:
get "users/:id" => "users#show"
In my Rails app I have users who can have many projects which in turn can have many tasks.
model:
class Task < ActiveRecord::Base
attr_accessible :project_id
end
controller:
class TasksController < ApplicationController
def create
#task = current_user.tasks.build(params[:task])
if #task.save
flash[:success] = "Task saved."
redirect_to edit_task_path(#task)
else
render :new
end
end
def update
if #task.update_attributes(params[:task])
flash[:success] = "Task updated."
redirect_to edit_task_path(#task)
else
render :edit
end
end
end
What's the standard practice in Rails to ensure that a user A can not create a task for a user B?
Right now, I am restricting the project_ids that are available to a user through the select box options in the form. However, this can be easily hacked through a browser console and is not safe at all.
How can this be improved?
Thanks for any help.
I would go with a before filter that checks if required project belongs to current user :
class TasksController < ApplicationController
before_filter :find_project, only: :create
def create
#task = #project.tasks.build(params[:task])
if #task.save
flash[:success] = "Task saved."
redirect_to edit_task_path(#task)
else
render :new
end
end
private
def find_project
#project = current_user.projects.where( id: params[ :task ][ :project_id ] ).first
redirect_to( root_path, notice: 'No such project' ) unless #project
end
end
So, if given project_id does not match a project belonging to current user, he is redirected out.
A more rails way, though, would be to use nested resources :
resources :projects
resources :tasks, shallow: true
end
You would have routes like this :
GET /projects/1/tasks (index)
GET /projects/1/tasks/new (new)
POST /projects/1/tasks (create)
GET /tasks/1 (show)
GET /tasks/1/edit (edit)
PUT /tasks/1 (update)
DELETE /tasks/1 (destroy)
But this won't differ much, you still have to retrieve Post :
class TasksController < ApplicationController
before_filter :find_project, only: [ :index, :new, :create ]
before_filter :find_task, only: [ :show, :edit, :update, :delete ]
# other actions
def create
#task = #project.tasks.build(params[:task])
if #task.save
flash[:success] = "Task saved."
redirect_to edit_task_path(#task)
else
render :new
end
end
private
def find_project
#project = current_user.projects.where( id: params[ :project_id ] ).first
redirect_to( root_path, notice: 'No such project' ) unless #project
end
def find_task
#task = current_user.tasks.where( id: params[ :id ] ).first
redirect_to( root_path, notice: 'No such task' ) unless #task
end
end
The easiest thing to do is scope your lookup and exploit the fact that #find can raise RecordNotFound. Rails will rescue that exception and render 404 for you.
class TasksController < ApplicationController
helper_method :project
def create
#task = project.tasks.build(params[:task])
if #task.save
flash[:success] = "Task saved."
redirect_to edit_task_path(#task)
else
render :new
end
end
private
def project
#project ||= current_user.projects.find(params[:task][:project_id])
end
end
I would also add that you should also scope the URL for tasks under the project it belongs to. Something like /projects/:project_id/tasks/:id using nested resources.