I'm new to rspec and writing test. I need to write a test for the show action of my VideosController. Here is my test
describe VideosController do
describe "GET show" do
it "renders the show template" do
video = Video.create(title: "Game of Thrones", description: "A very awesome show!")
get :show, id: video.id
expect(response).to render_template(:show)
end
end
end
When I run this I get this error
1) VideosController GET show renders the show template
Failure/Error: expect(response).to render_template(:show)
expecting <"show"> but rendering with <[]>
What am I missing here?
EDIT: VideosController
class VideosController < ApplicationController
before_action :require_user
def show
#video = Video.find(params[:id])
end
def search
#search_phrase = params[:search_term]
#search_result = Video.search_by_title(#search_phrase)
end
end
For setting the user, controller tests are meant to be isolated from other aspects of the app. So really you can simply stub the user. Provided you're using a method in your application controller like:
def current_user
#current_user ||= User.find session[:user_id]
end
Then you can just stub it:
controller.stub(current_user: User.create(email: "test#example.com", password: "abc123", password_confirmation: "abc123"))
Adjust the user params to your need. Also instead of invoking active record directly, have a look at: http://github.com/thoughtbot/factory_girl.
So what happens there is when the controller invokes current_user, it returns the user record.
Related
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
My controller test isn't passing. I have a before_acton in my ReviewsController that tests for a current user. If there is none, it shouldn't complete the create action and redirect. But it does create it, despite the session[:user_id] being nil. Here is my code:
it "doesnt create review if there is valid input, but not authenticated" do
video = Video.create(title: "new title", description: "new description")
review = Review.create(content: "content1", rating: 1)
expect(Review.count).to eq(0)
end
In my ReviewsController:
def create
#video = Video.find(params[:video_id])
#review = Review.new(parameters)
rating = params[:review][:rating]
content = params[:review][:content]
#review.content = content
#review.rating = rating
#review.user_id = current_user.id
#review.video = #video
#review.save
redirect_to #video
end
i have a before_action in the Reviews controller that test if a user is authenticated. If the session[:user_id] is nil, then it redirects to the sign_in_path. Here are the methods:
def current_user
#current_user = User.find(session[:user_id]) if session[:user_id]
end
def require_user
redirect_to sign_in_path unless current_user
end
in my reviews_controller_spec.rb file, it shouldn't create the review, because it shouldn't get past the before_action :require_user
Why is the review object still being created? Why doesn't the before_action filter stop it? The test fails and just says it expected Review.count to be 0, but got 1.
# config/routes.rb
resources :videos, shallow: true do
resources :reviews
end
There is no need to bind params to attributes 1-1. In fact doing so will make you controllers excessively verbose plus its boring as hell to type it out.
class ReviewsController < ApplicationController
before_filter :authenticate_user!
def create
#video = Video.find(params[:video_id])
#review = current_user.reviews.new(review_params) do |r|
r.video = #video
end
end
private
# this is mass assignment protection
def review_params
params.require(:review).permit(:rating, :content)
end
end
If you decide to roll you own authentication (which is a bad idea) don't repeat it across your controllers and views:
module AuthenticationHelper
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def sign_in(user)
#current_user = user
session[:user_id] = user.id
end
def sign_out
#current_user = nil
reset_session
end
def authenticate_user!
raise User::NotAuthorized unless current_user
end
end
A key point is that a opt out security model is far better since it eliminates the risk of leaving a route open by omission:
class ApplicationController
include AuthenticationHelper
rescue_from User::NotAuthorized, with: :deny_access!
# use skip_before_action :authenticate_user! if
# you don't want a route / controller to require auth.
before_action :authenticate_user!
private
def deny_access!
redirect_to root_path
end
end
So to test ReviewsController we would do:
describe ReviewsController do
let(:user) { User.create(name: 'joe') }
let(:video) { Video.create(name: 'Cats Gone Wild') }
describe "#create" do
let(:valid_attributes) { { video_id: video, title: "new title", description: "new description" } }
context "when unauthorized" do
it "does not create a review" do
expect { post :create, valid_attributes }.to_not change(Review, :count)
end
end
context "when authorized" do
before { subject.stub(:current_user) { user } }
# ...
end
end
end
Key points here:
use let to setup test dependencies
use expect { action }.to change to test if an action alters the DB. Testing for .count == 0 can lead to false positives.
I have a user model (agents) in my app who can create posts. They can do this from their dashboard. I also show all their existing posts within their dashboard. The problem comes when trying to create a new post with no content which I'm testing to make sure it renders an error. Creating a post with content works but creating one with out keeps triggering an error which says my #posts instance variable is nil. Not sure if I missed something?
Dashboard view:
.container
.column.span9
- if current_agent
= render 'home/shared/agent_post_panel'
= render 'home/shared/agent_dashboard_tabs'
agent_dashboard_tabs:
.tabs-container
#posts
.content
- if #posts.any? //This is where the error is triggered
= render partial: 'shared/post', collection: #posts
Controller for Dashboard:
class HomeController < ApplicationController
before_filter :authenticate!, only: [:dashboard]
def index
end
def dashboard
if current_agent
#post = current_agent.posts.build
#posts = current_agent.posts
end
end
end
My post controller:
class PostsController < ApplicationController
before_filter :authenticate_agent!
def create
#post = current_agent.posts.build(params[:post])
if #post.save
flash[:notice] = "Post created!"
redirect_to dashboard_path
else
flash[:error] = "Post not created"
render 'home/dashboard'
end
end
end
Tests:
feature 'Creating posts' do
let(:agent) { FactoryGirl.create(:agent) }
before do
sign_in_as!(agent)
visit dashboard_path
end
scenario "creating a post with valid content" do
fill_in 'post_content', :with => 'I love donuts'
expect { click_button "Post" }.to change(Post, :count).by(1)
end
scenario "creating a post with invalid content" do
expect { click_button "Post" }.not_to change(Post, :count)
click_button "Post"
page.should have_content("Post not created")
page.should have_content("can't be blank")
end
You are rendering home/dashboard from within posts#create when the post has errors, but you are not setting #posts variable.
You should add this before render 'home/dashboard'
#posts = current_agent.posts
Use redirect_to dashboard_path instead of render 'home/dashboard'.
and you can keep flash message on dashboard by calling keep method of flash in home#dashboard.
flash.keep
If I understand your question correctly, You are having an issue in the loading the page (HomeController#index)
As I can see prior to load your index action, you check if the user is logged in or not,
make sure your application flow goes into
if current_agent
#make sure you application flow comes here
#post = current_agent.posts.build
#posts = current_agent.posts
end
If not you might want to slightly modify this flow as (if this fits your requirment)
if current_agent
#post = current_agent.posts.build
#posts = current_agent.posts
else
#post = Post.new
#posts = []
end
and finally its always good to use try, when you are not sure about the object you get
if #posts.try(:any?)
I have written this controller code in Ruby on Rails
class PostsController < ApplicationController
before_filter :authenticate_user!
def index
#posts = Post.all(:order => "created_at DESC")
respond_to do |format|
format.html
end
end
def create
#post = Post.create(:message => params[:message])
respond_to do |format|
if #post.save
format.html { redirect_to posts_path }
format.js
else
flash[:notice] = "Message failed to save."
format.html { redirect_to posts_path }
end
end
end
end
and corresponding to this I have written the following test case :-
require 'spec_helper'
describe PostsController do
describe "GET 'index'" do
it "returns http success" do
get 'index'
response.should be_success
end
end
describe "#create" do
it "creates a successful mesaage post" do
#post = Post.create(message: "Message")
#post.should be_an_instance_of Post
end
end
end
I am getting failures on both. Please take a look on the code and help me figure out.
I suspect you are not logged in since you are using Devise?
Maybe you need to include the devise testhelpers:
describe PostsController do
include Devise::TestHelpers
before(:each) do
#user = User.create(...)
sign_in #user
end
#assertions go here
end
As Tigraine states, it appears as though you probably are not logged in (with Devise) when the tests get executed. However, showing the failures would help in narrowing down the problem further.
On top of that, the second test isn't really an integration test and I would probably prefer something like the following to test the same condition. There are two types of test you could do:
# inside 'describe "#create"'
let(:valid_params) { {'post' => {'title' => 'Test Post'} }
it 'creates a new Post' do
expect {
post :create, valid_params
}.to change(Post, :count).by(1)
end
# and / or
it 'assigns a new Post' do
post :create, valid_params
assigns(:post).should be_a(Post)
assigns(:post).should be_persisted
end
Don't forget to add this line into your spec_helper.rb
require "devise/test_helpers"
include Devise::TestHelpers
Nevertheless, here is link for Devise wiki - How to test Controllers where you can find more info about this approach. I recommend writing the before method without (:each), what I remember it sometimes causes problems.
before do
#user = FactoryGirl.create(:user)
sign_in #user
end
Can always use:
puts response.inspect
To see how your response looks like.
I am using Ruby on Rails 3.2.2, Rspec 2.9.0 and RspecRails 2.9.0. I am trying to test a new controller action and I would like to know why I get the error explained above only for that action.
Given:
# controller
class ArticlesController < ApplicationController
before_filter :signed_in
def new
#article = Article.new
# This is just a sample code line to show you where the error happens?
#article.new_record?
...
end
def show
#article = Article.find(params[:id])
...
end
end
# spec file
require 'spec_helper'
describe ArticlesController do
before(:each) do
#current_user = FactoryGirl.create(:user)
# Signs in user so to pass the 'before_filter'
cookies.signed[:current_user_id] = {:value => [#current_user.id, ...]}
end
it "article should be new" do
article = Article.should_receive(:new).and_return(Article.new)
get :new
assigns[:article].should eq(article)
end
it "article should be shown" do
article = FactoryGirl.create(:article)
get :show, :id => article.id.to_s
assigns[:article].should eq(article)
end
end
When I run the Example related to the new action I get this error (it is related to the #article.new_record? code line in the controller file):
Failure/Error: get :new
NoMethodError:
undefined method `new_record?' for nil:NilClass
But when I run the Example related to the show action it passes without errors.
What is the problem? How can I solve that?
The problem is the way you've done
Article.should_receive(:new).and_return(Article.new)
This is the same as
temp = Article.should_receive(:new)
temp.and_return(Article.new)
So by the time you are setting up the return value, Article.new has already been mocked out and so returns nil, so you're doing and_return(nil) Create the return value first, i.e.
new_article = Article.new #or any other way of creating an article - it may also be appropriate to return a mock
Article.should_receive(:new).and_return(new_article)
Try:
it "article should be new" do
article = FactoryGirl.build(:article)
Article.stub(:new).and_return(article)
get :new
assigns(:article).should == article
end