m new to rails and rspec
I have a controller with destroy action
before_action :authorize_user, only: %i[edit update destroy]
def destroy
#question.destroy
respond_to do |format|
format.html { redirect_to questions_url, notice: 'Question was successfully destroyed.' }
format.json { head :no_content }
end
end
I have a private method
def authorize_user
redirect_to root_path if #question.user_id != current_user.id
end
This is the rspec test case I have written
describe "DELETE /destroy" do
context 'When user has signed in ' do
let!(:user) { create(:user) }
before do
sign_in(user)
end
context 'User who created the question can destroy' do
it "destroys the requested question" do
expect {
question = create(:question, user: user)
# question.user = user
# delete :destroy
delete question_url(question)
}.to change(Question, :count).by(-1)
end
end
end
end
I am getting error like this
expected `Question.count` to have changed by -1, but was changed by 0
You can't create your question in the expect block, because then you get a total of 0 count changes (0 before you create, +1 for the create, -1 for your destroy action). If you move that line outside the expect block, I suspect your test will pass.
question = create(:question, user: user)
expect { delete question_url(question) }.to change(Question, :count).by(-1)
Related
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.
The idea of this test is to handle the event a record is not found. There for a destroy action should not take place and yet it is. I've chopped and changed it around, nut essentially the test reads right, so there must be a problem in my code.
RSpec.describe Admin::EntriesController, :type => :controller do
setup_factories
let(:model){ Photo }
let(:instance){photo}
let(:no_instances){no_photos}
let(:some_instances){some_photos}
let(:params_full_instance){params_full_photo}
let(:params_new_instance){params_new_photo}
describe "delete destroy" do
context "where the record is found" do
before do
allow(model).to receive(:where).and_return(some_instances)
allow(some_instances).to receive(:first).and_return(instance)
end
context "where the record is destroyed" do
before do
allow(instance).to receive(:destroy).and_return(true)
delete :destroy, params_id
end
sets_flash(:notice)
redirects_to('/admin/entries/rejected')
end
context "where the record is not destroyed" do
before do
allow(instance).to receive(:destroy).and_return(false)
delete :destroy, params_id
end
sets_flash(:error)
redirects_to('/admin/entries/rejected')
end
end
context "where the record is not found" do
before do
allow(model).to receive(:where).and_return(no_instances)
delete :destroy, params_id
end
sets_flash(:error)
redirects_to('/admin/entries/rejected')
end
CONTROLLER
before_action :get_entry_id, only: [:destroy, :toggle_approve_field, :toggle_reject_field]
def destroy
if #entry.nil?
flash[:error] = "object is not not found"
end
if #entry.destroy
flash[:notice] = 'blah'
respond_to do |format|
format.html { redirect_to rejected_admin_entries_path }
format.json { head :no_content }
format.js
end
else
flash[:error] = 'There was a problem fetching the record'
redirect_to rejected_admin_entries_path
end
end
def get_entry_id
if #entry.nil?
flash[:error] = "object is not not found"
end
#entry = Photo.where(id: params[:id]).first
end
I probably need a fresh pair of eyes on it. And a nice explanation as to what I am getting wrong ;)
UPDATE: expected behaviour, is, when a destroy action is made, and the record is not found, i expect to see a flash error and to be redirected.
UPDATE 2: Implemented guys code below, still receiving error
1) Admin::EntriesController for authenticated users delete destroy where the record is not found should set flash error
Failure/Error: expect(flash[method]).to_not be_nil
expected: not nil
got: nil
# -e:1:in `<main>'
2) Admin::EntriesController for authenticated users delete destroy where the record is not found should redirect to /admin/entries/rejected
Failure/Error: expect(response).to redirect_to(path)
Expected response to be a <redirect>, but was <200>
Thanks
Rico
Your problem is you are still trying #entry.destroy even if it is still nil.
def destroy
if #entry.nil?
flash[:error] = "object is not not found"
elsif #entry.destroy
flash[:notice] = 'blah'
respond_to do |format|
format.html { redirect_to rejected_admin_entries_path }
format.json { head :no_content }
format.js
end
else
flash[:error] = 'There was a problem fetching the record'
redirect_to rejected_admin_entries_path
end
end
The problem is in the destroy method. You are not calling return. This should work:
def destroy
if #entry.nil?
flash[:error] = "object is not not found"
return
end
if #entry.destroy
flash[:notice] = 'blah'
respond_to do |format|
format.html { redirect_to rejected_admin_entries_path }
format.json { head :no_content }
format.js
end
else
flash[:error] = 'There was a problem fetching the record'
redirect_to rejected_admin_entries_path
end
end
Anyway this code is sub-optimal. You should find the #entry in a before_filter aspect and return a 404 if is not found.
E.g.,
before_filter :find_entry
...
def find_entry
#entry = Entry.find(params[:id])
end
I believe I am having problems with the redirect_to in my controller below. A course has many levels, which has many steps (step belongs to level, and level belongs to course). I am trying to redirect_to the step.
Here is my route for a step:
/courses/:course_id/levels/:level_id/steps/:id(.:format)
Here is my error in the controller:
Failure/Error: click_button "check answer"
NoMethodError:
undefined method `step_url' for #<UserStepsController:0x007ff8ccc478c8>
Here is the controller in which the error occurs:
class UserStepsController < ApplicationController
before_filter :authenticate_user!
def create
#step = Step.find(params[:user_step][:step_id])
current_user.attempt_step!(#step)
respond_to do |format|
format.html { redirect_to #step }
format.js
end
end
def destroy
#step = UserStep.find(params[:id]).step
current_user.remove_user_step!(#step)
respond_to do |format|
format.html { redirect_to #step }
format.js
end
end
end
Here is the rspec
describe "attempting a step" do
let(:course) { FactoryGirl.create(:course)}
let(:level) { FactoryGirl.create(:level, course: course) }
let(:step) { FactoryGirl.create(:step, level: level) }
before { sign_in user}
describe "taking a course" do
before { visit course_level_step_path(course.id, level.id, step.id)}
it "should increment the user step count" do
expect do
click_button "check answer"
end.to change(user.user_steps, :count).by(1)
end
describe "toggling the button" do
before { click_button "check answer"}
it { should have_selector('input', value: 'remove step')}
end
end
You can't use redirect_to #variable when you have nested resources. The correct way would be for you to say
redirect_to course_level_step_url(#step.level.course.id, #step.level.id, #step.id)
Because the thing that gets the route from the instance variable 'thinks' you want the path of the lowercased class name plus _url
In my controller when an user creates a new post, he/she is redirected to the page that contains the newly created post. I'm wanting to create a test in rspec to cover this redirect but am having trouble with it. Specifically, I want to know what to write in the refirst_to argument. Here is the controller code below..
def create
#micropost = Micropost.new(params[:micropost])
respond_to do |format|
if #micropost.save
format.html {redirect_to #micropost}
else
format.html {render action: 'edit'}
end
end
end
Here is the rspec test...
before do
#params = FactoryGirl.build(:micropost)
end
it "redirects to index" do
#clearly #params.id doesn't work. its telling me instead of a redirect im getting a
#200
#response.should redirect_to(#params.id)
end
Assuming that #params will create a valid Micropost (otherwise .save will fail and you'll be rendering :edit)...
it "redirects to index on successful save" do
post :create, :micropost => #params.attributes
response.should be_redirect
response.should redirect_to(assigns[:micropost])
end
it "renders :edit on failed save" do
post :create, :micropost => {}
response.should render ... # i don't recall the exact syntax...
end
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.