Testing comments controller. I cant pass test for create comment - ruby-on-rails

I want to test comments controller, action create, but I don't know how what's wrong with it. Test are not save comment
comments_controller.rb is work in my projetc, i can see all comments by rails console (as like Comments.all. So that valid:
class CommentsController < ApplicationController
before_action :authenticate_user!, only:[:create,:vote]
before_action :show, only:[:show,:vote]
respond_to :js, :json, :html
def create
#comment = Comment.new(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to post_path(#comment.post.id)
else
redirect_to root_path
end
end
private
def comment_params
params.require(:comment).permit(:comment, :post_id)
end
end
comments_controller_spec.rb is here. It seems like that i send bad params
require 'rails_helper'
RSpec.describe CommentsController, type: :controller do
let(:user) {create :user}
let(:params) { {user_id: user} }
before {sign_in user}
describe '#create' do
let(:post) {create :post}
let(:params) do
{
user_id: user.id,
post_id: post.id,
comment: attributes_for(:comment)
}
end
subject {process :create, method: :post, params: params}
it 'create comment' do
expect{subject}.to change {Comment.count}.by(1)
# is_expected.to redirect_to(user_post_path(assigns(:user), assigns(:post)))
end
end
end
factory comments.rb is here:
FactoryBot.define do
factory :comment do
association :post
association :user
user_id { FFaker::Internet.email }
comment { FFaker::Lorem.sentence }
end
end

Related

RSpec testing destroy action

I'm trying to test the 'destroy' action for my nested comments controller.
User model has_many :comments, dependent: :destroy
Movie model has_many :comments, dependent: :destroy
Comments model belongs_to :user and :movie
Here is my comments controller
def create
#comment = #movie.comments.new(comment_params.merge(user: current_user))
if #comment.save
flash[:notice] = 'Comment successfully added'
redirect_to #movie
else
flash.now[:alert] = 'You can only have one comment per movie'
render 'movies/show'
end
end
def destroy
#comment = #movie.comments.find(params[:id])
if #comment.destroy
flash[:notice] = 'Comment successfully deleted'
else
flash[:alert] = 'You are not the author of this comment'
end
redirect_to #movie
end
private
def comment_params
params.require(:comment).permit(:body)
end
def set_movie
#movie = Movie.find(params[:movie_id])
end
Of course there is also before_action :set_movie, only: %i[create destroy] at the top.
Here are my specs, I'm using FactoryBot and all factories works fine in other examples so I think the issue is somewhere else.
describe "DELETE #destroy" do
let(:user) { FactoryBot.create(:user) }
let(:movie) { FactoryBot.create(:movie) }
before do
sign_in(user)
end
it "deletes comment" do
FactoryBot.create(:comment, movie: movie, user: user)
expect do
delete :destroy, params { movie_id: movie.id }
end.to change(Comment, :count).by(-1)
expect(response).to be_successful
expect(response).to have_http_status(:redirect)
end
end
I've got an error ActionController::UrlGenerationError: No route matches {:action=>"destroy", :controller=>"comments", :movie_id=>1}
I think my address in specs destroy action is wrong but how to define it in a good way?
You need to specify id of a comment you want to remove:
it "deletes comment" do
comment = FactoryBot.create(:comment, movie: movie, user: user)
expect do
delete :destroy, params { id: comment.id, movie_id: movie.id }
end.to change(Comment, :count).by(-1)
# ...
end
I want to contribute here with one more approach. Sometimes you have to be sure that you've deleted the exact instance (comment_1, not comment_2).
it "deletes comment" do
comment_1 = FactoryBot.create(:comment, movie: movie, user: user)
comment_2 = FactoryBot.create(:comment, movie: movie, user: user)
delete :destroy, params { id: comment_1.id, movie_id: movie.id }
expect { comment_1.reload }.to raise_error(ActiveRecord::RecordNotFound)
# ...
end
Somehow I was never able to pass an ID, either in params {} or as a direct argument following "delete". Instead, I made it work like so:
it "DELETE will cause an exception" do
delete "/comments/#{comment.id}"
expect { comment.reload }.to raise_error(ActiveRecord::RecordNotFound)
end

RSpec controller: redirect_to other controller

I have this issue with test my CommentsController:
Failure/Error: redirect_to user_path(#comment.user), notice: 'Your
comment was successfully added!' ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"users", :id=>nil}
missing required keys: [:id]
This is my method in my controller:
def create
if params[:parent_id].to_i > 0
parent = Comment.find_by_id(params[:comment].delete(:parent_id))
#comment = parent.children.build(comment_params)
else
#comment = Comment.new(comment_params)
end
#comment.author_id = current_user.id
if #comment.save
redirect_to user_path(#comment.user), notice: 'Your comment was successfully added!'
else
redirect_to user_path(#comment.user), notice: #comment.errors.full_messages.join
end
end
This is my RSpec:
context "User logged in" do
before :each do
#user = create(:user)
sign_in #user
end
let(:comment) { create(:comment, user: #user, author_id: #user.id) }
let(:comment_child) { create(:comment_child, user: #user, author_id: #user.id, parent_id: comment.id) }
describe "POST #create" do
context "with valid attributes" do
it "saves the new comment object" do
expect{ post :create, comment: attributes_for(:comment), id: #user.id}.to change(Comment, :count).by(1)
end
it "redirect to :show view " do
post :create, comment: attributes_for(:comment), user: #user
expect(response).to redirect_to user_path(comment.user)
end
end
...
end
end
My Comment model:
class Comment < ActiveRecord::Base
belongs_to :user
acts_as_tree order: 'created_at DESC'
VALID_REGEX = /\A^[\w \.\-#:),.!?"']*$\Z/
validates :body, presence: true, length: { in: 2..240}, format: { with: VALID_REGEX }
end
How Can I add user_id to that request? When I change code in my controller redirect_to user_path(#comment.user) to redirect_to user_path(current_user) - test pass. May I redirect_to user in comments controller? Is any posibility to do it right? Thanks for your time.
Basically the error is caused by the fact that the #comment.user is nil.
Lets start fixing it by cleaning up the spec:
context "User logged in" do
# declare lets first.
let(:user) { create(:user) }
let(:comment) { create(:comment, user: user, author: user) }
# use do instead of braces when it does not fit on one line.
let(:comment_child) do
# use `user: user` instead of `user_id: user.id`.
# the latter defeats the whole purpose of the abstraction.
create(:comment_child, user: user, author: user, parent: comment)
end
before { sign_in(user) }
describe "POST #create" do
context "with valid attributes" do
it "saves the new comment object" do
expect do
post :create, comment: attributes_for(:comment)
end.to change(Comment, :count).by(1)
end
it "redirects to the user" do
post :create, comment: attributes_for(:comment)
expect(response).to redirect_to user
end
end
end
end
You should generally avoid using instance vars and instead use lets in most cases. Using a mix just adds to the confusion since its hard to see what is lazy loaded or even instantiated where.
Then we can take care of the implementation:
def create
#comment = current_user.comments.new(comment_params)
if #comment.save
redirect_to #comment.user, notice: 'Your comment was successfully added!'
else
# ...
end
end
private
def comment_params
# note that we don't permit the user_id to be mass assigned
params.require(:comment).permit(:foo, :bar, :parent_id)
end
Basically you can cut a lot of the overcomplication:
Raise an error if there is no authenticated user. With Devise you would do before_action :authenticate_user!.
Get the user from the session - not the params. Your not going to want or need users to comment on the behalf of others.
Wrap params in the comments key.
Use redirect_to #some_model_instance and let rails do its polymorpic routing magic.
Let ActiveRecord throw an error if the user tries to pass a bad parent_id.
Also does your Comment model really need both a user and author relationship? Surely one of them will suffice.

Testing that a user can't update/destroy another user's comment

In my small app , users can post comments. These comments can be destroyed only by their owners. I am trying to log in a user, create a comment, log out a user and then try to delete the comment that the first user created. However this action succeds for some reason. This is my comments controllor only showing the create and update actions and private methods:
module Api
module V1
class CommentsController < Api::V1::BaseController
before_action :check_user
before_action :get_comment, only: [:destroy, :update]
respond_to :json
def destroy
if #comment.destroy
head :no_content, status: :no_content
else
render json: serialize_model(#comment.errors)
end
end
def update
if #comment.update_attributes(comment_params)
render json: serialize_model(#comment), status: :accepted
else
render json: { errors: #comment.errors }, status: :bad_request
end
end
private
def comment_params
params.require(:comment).permit(:text, :picture_id)
end
def get_comment
#comment = Comment.find_by_id(params[:id])
check_owner
end
def check_user
render json: { errors: { user: "not signed in" } }, status: :unauthorized unless user_signed_in?
end
def check_owner
render json: { errors: { user: "not the owner" } }, status: :unauthorized unless current_user.id = #comment.id
end
end
end
end
These are my shared exmples for the test:
shared_context 'comments' do
def setup_requirements_without_login
#user = FactoryGirl.create(:user)
#category = FactoryGirl.create(:category)
#picture = FactoryGirl.create(:picture, category_id: #category.id, user_id: #user.id)
end
def setup_requirements_with_login
setup_requirements_without_login
sign_in(#user)
end
shared_examples 'not the owner' do
it 'creates a resource' do
body = JSON.parse(response.body)
expect(body).to include('errors')
data = body['errors']
expect(data).to include('user')
end
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
And these are the tests for update and destroy action:
require "rails_helper"
include Warden::Test::Helpers
Warden.test_mode!
RSpec.describe Api::V1::CommentsController, type: :controller do
include_context 'comments'
describe 'PATCH /api/comments/:id' do
context 'when it is a valid request' do
let(:attr) do
{ text: 'update' }
end
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
patch :update, id: #comment.id, comment: attr , format: :json
end
it 'creates a resource' do
body = JSON.parse(response.body)
expect(body).to include('data')
data = body['data']
expect(data['attributes']['text']).to eq('update')
end
it 'responds with 202' do
expect(response).to have_http_status(202)
end
end
context 'when the user is not logged in' do
let(:attr) do
{ text: 'update' }
end
before(:each) do
setup_requirements_without_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
patch :update, id: #comment.id, comment: attr , format: :json
end
it_behaves_like "not logged in"
end
context 'when the user is not the owner' do
let(:attr) do
{ text: 'update' }
end
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
sign_out(#user)
logout
#user2 = FactoryGirl.create(:user)
sign_in(#user2)
patch :update, id: #comment.id, comment: attr , format: :json
end
it_behaves_like "not the owner"
end
end
describe 'DELETE /api/comments/:id' do
context 'when it is a valid request' do
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
delete :destroy, id: #comment.id, format: :json
end
it 'responds with 204' do
expect(response).to have_http_status(204)
end
end
context 'when the user is not logged in' do
before(:each) do
setup_requirements_without_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
delete :destroy, id: #comment.id, format: :json
end
it_behaves_like "not logged in"
end
context 'when the user is not the owner' do
before(:each) do
setup_requirements_with_login
#comment = FactoryGirl.create(:comment, picture_id: #picture.id, user_id: #user.id)
sign_out(#user)
logout
#user2 = FactoryGirl.create(:user)
sign_in(#user2)
delete :destroy, id: #comment.id, format: :json
end
it_behaves_like "not the owner"
end
end
end
My problem is that the action succeeds when it shouldn't for some reason. I use pry to debugg and it makes me question the tests even more because it says current_user has the id of 97 when the test created users with the ids: 1001 and 1002 which is very odd... . Did I make a mistake in the controller ? or tests?
your check_owner function should have == instead of = in its unless condition:
unless current_user.id == #comment.id
Otherwise the id from the #comment gets assigned to current_user.id. This is probably the origin for your 97. =)
def get_comment
#comment = current_user.comments.find! params[:id]
end
This automatically adds the association to the SQL query (where user_id=1337) and the bang method (with the !) throws an 404 Exception if record wasnt found. That is the easiest way to controll that only the owner has access to its own records.

RSPEC test index action with before_action filter

I have a before_action filter and want to test that the index action is only executed if the user is logged in. Simply put, i don't know how to do this. I'm using my own simple authentication and i know i could use CanCan or similar but for my own learning i'm doing it the hard way!
ApplicationController.rb
helper_method :logged_in
helper_method :current_user
def current_user
#current_user ||= User.find_by_id(session[:current_user]) if session[:current_user]
end
def logged_in
unless current_user
redirect_to root_path
end
end
ActivitiesController.rb
before_action :logged_in
def index
#activities = Activity.all.where(user_id: #current_user)
end
Activities_Controller_spec.rb
require 'rails_helper'
RSpec.describe ActivitiesController, :type => :controller do
describe "GET index" do
before(:each) do
#activity = FactoryGirl.create(:activity)
session[:current_user] = #activity.user_id
#current_user = User.find_by_id(session[:current_user]) if session[:current_user]
end
it "shows all activities for signed in user" do
get :index, {user_id: #activity.user_id}
expect(response).to redirect_to user_activities_path
end
end
end
activities.rb(Factory)
FactoryGirl.define do
factory :activity do
association :user
title { Faker::App.name }
activity_begin { Faker::Date.forward(10) }
activity_end { Faker::Date.forward(24) }
end
end
I'm getting the following error:
Failure/Error: expect(response).to redirect_to user_activities_path
Expected response to be a redirect to <http://test.host/users/1/activities> but was a redirect to <http://test.host/>.
Expected "http://test.host/users/1/activities" to be === "http://test.host/".
After long discussion I think tests should be smth like this (it is not tested :) )
require 'rails_helper'
RSpec.describe ActivitiesController, :type => :controller do
describe "GET index" do
before(:each) do
#activity = FactoryGirl.create(:activity)
end
context 'when user is logged' do
before(:each) do
session[:current_user] = #activity.user_id
end
it "shows all activities for signed in user" do
get :index, {user_id: #activity.user_id}
expect(response).to be_success
end
end
context 'when user is anonymous' do
it "redirects user to root path" do
get :index, {user_id: #activity.user_id}
expect(response).to redirect_to root_path
end
end
end
end

expecting <"edit"> but rendering with <[]>

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

Resources