wrong number of arguments (given 2, expected 0) in controller spec - ruby-on-rails

I want to check whether the create action passes.
describe "#create" do
let!(:user){ create(:user) }
let!(:post){ create_list(:post, 3, user: user) }
context "authenticated user" do
it "adds a new post" do
post_params = FactoryBot.attributes_for(:post)
sign_in user
expect{ post :create, params: {post: post_params} }.to change(user.posts, :count).by(1)
end
end
end
but my error is
Failure/Error: expect{ post :create, params: {post: post_params} }.to change(user.posts, :count).by(1)
ArgumentError:
wrong number of arguments (given 2, expected 0)
posts_controller:
def create
#post = Post.new(post_params)
if params[:images]
if #post.save
params[:images].each do |img|
#post.photos.create(image: img)
end
else
end
redirect_to posts_path
flash[:notice] = "success"
else
redirect_to posts_path
flash[:alert] = "failure"
end
end

It's most probably because the variable you are using, let!(:post), is clashing with post :create, params: {post: post_params} (the post method).
Solution is to change let!(:post) to let!(:posts) since it is a list anyway.

Related

Rspec check the template after a redirect

Here is my create action for users:
def create
#user = User.new(user_params)
if #user.save
respond_to do |format|
format.html {
redirect_to edit_admin_user_path(#user)
flash[:success] = "Successfully created"
}
end
else
render :new
flash[:alert] = "Something went wrong"
end
end
My test is looking like this:
context "POST methods" do
describe "#create" do
it "renders the edit template" do
post :create, user: FactoryGirl.attributes_for(:user)
expect(response).to render_template(:edit)
end
end
end
However I'm getting this error:
Failures:
1) Admin::UsersController POST methods #create renders the edit template
Failure/Error: expect(response).to render_template(:edit)
expecting <"edit"> but was a redirect to <http://test.host/admin/users/80/edit>
I want to check if the edit.html.haml file is rendered after creating a user. What am I doing wrong?
Update #1
I do check for redirect in another test, this is my full test suite:
require 'rails_helper'
RSpec.describe Admin::UsersController, type: :controller do
render_views
context "POST methods" do
describe "#create" do
it "using valid params" do
expect{
post :create, user: { email: "something#hello.com", password: "long12345678" }
}.to change(User, :count).by(1)
# get user_path('1')
end
it "redirects to the edit page after saving" do
post :create, user: FactoryGirl.attributes_for(:user)
user = User.last
expect(response).to redirect_to(edit_admin_user_path(user.id))
end
it "renders the edit template" do
post :create, user: FactoryGirl.attributes_for(:user)
user = User.last
expect {
redirect_to(edit_admin_user_path(user.id))
}.to render_template(:edit)
end
context "it redirects to new" do
it "if user has no valid mail" do
post :create, user: { email: "something", password: "long12345678" }
expect(response).to render_template(:new)
end
it "if user has no valid password" do
post :create, user: { email: "something#mail.com", password: "short" }
expect(response).to render_template(:new)
end
end
end
end
end
What I want is to actually check if the edit template is rendered. Because with expect(response).to redirect_to(edit_admin_user_path(user)) it does not check the template. This test passes even if I have no edit.html.haml file at all.
When you're testing create action you should just check correctness of redirect. In this action you're not actually rendering edit template, but you're just making redirect to the edit path of created entity. So this is the thing you should check.
describe "#create" do
it "redirects to the edit path" do
post :create, user: FactoryGirl.attributes_for(:user)
expect(response).to redirect_to(edit_admin_user_path(User.last))
end
end
Then you should have another test for edit action, where you're checking template rendering. That will mean that after redirect in create action you also will see the proper template.
You are redirecting to edit_admin_user_path after successfully saving the User in your controller action. But, you're testing render in the test instead.
Update your test as below.
context "POST methods" do
describe "#create" do
before(:each) do
#user = FactoryGirl.attributes_for(:user)
end
it "renders the edit template" do
post :create, user: #user
expect(response).to redirect_to(edit_admin_user_path(#user))
end
end
end

How to use data from one factory in another factory

I'm trying to test my app so that when a user votes for a certain kind of food, both the count of user.votes goes up as well as food.votes. As of now I can't that to happen, and I'm not sure if I've written my tests wrong or have set up a factory that will not make it possible for the tests to pass. It works when I test manually in the console, so I know that it does function, but the tests do not agree.
FactoryGirl.define do
factory :vote do |f|
f.user_id user
f.food_id food
end
end
def create
#vote = Vote.new(vote_params)
if #vote.save
flash[:notice] = "Thanks for voting!"
else
flash[:notice] = "Something went wrong"
end
end
before(:each) do
#user = create(:user)
sign_in(#user)
#food = create(:food)
end
describe "POST #create" do
context "with valid attributes" do
it "creates a new vote" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(Vote, :count).by(1)
end
it "Updates #user vote count" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
end
it "Updates #food vote count" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
end
end
end
Failures:
1) VotesController POST #create with valid attributes Updates #user vote count
Failure/Error: expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/votes_controller_spec.rb:27:in `block (4 levels) in <top (required)>'
2) VotesController POST #create with valid attributes Updates #food vote count
Failure/Error: expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/votes_controller_spec.rb:32:in `block (4 levels) in <top (required)>'
Finished in 2.99 seconds (files took 4.66 seconds to load)
36 examples, 2 failures
Failed examples:
rspec ./spec/controllers/votes_controller_spec.rb:25 # VotesController POST #create with valid attributes Updates #user vote count
rspec ./spec/controllers/votes_controller_spec.rb:30 # VotesController POST #create with valid attributes Updates #food vote count
Ok, confident enough to take a guess. The issue, I think, is that vote_params is coming from a factory (probably with hard-coded values for user_id/food_id) - but not the real #user and #food that you are testing against. You need to put these values into your vote_params can I suggest something like the following:
context "with valid attributes" do
# Set vote-params from factory, then merge in the #user/#food ids
let(:vote_params) { FactoryGirl.attributes_for(:vote).merge(:user_id => #user.id, :vote_id => #vote.id) }
it "creates a new vote" do
expect { post :create, params: { vote: vote_params } }.to change(Vote, :count).by(1)
end
it "Updates #user vote count" do
expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
end
it "Updates #food vote count" do
expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
end
end

rspec controller spec matchers

With which matcher and how can I test if the #post_comment and #post_comment.user is properly assigned?
expect(assigns(:post_comment)).to be_a_new(PostComment) is not working here.
UPDATE:
With the following setup I also get the following error. What should I change to be able to test the invalid attrs?
Posts::PostCommentsController when user is logged in POST create with invalid attributes doesn't save the new product in the db
Failure/Error: #post_comment.save!
ActiveRecord::RecordInvalid:
Validation failed: Body can't be blank
IF I delete #post_comment.save! then I get
Posts::PostCommentsController when user is logged in POST create with invalid attributes doesn't save the new product in the db
Failure/Error: <span class="post-comment-updated"><%= local_time_ago(post_comment.updated_at) %></span>
ActionView::Template::Error:
undefined method `to_time' for nil:NilClass
post_comments_controller
def create
#post = Post.find(params[:post_id])
#post_comment = #post.post_comments.build(post_comment_params)
authorize #post_comment
#post_comment.user = current_user
#post_comment.save!
if #post_comment.save
#post.send_post_comment_creation_notification(#post_comment)
#post_comment_reply = PostCommentReply.new
respond_to do |format|
format.html { redirect_to posts_path, notice: "Comment saved!" }
format.js
end
end
end
post_comments_controller_spec.rb
describe "POST create" do
let!(:profile) { create(:profile, user: #user) }
let!(:post_instance) { create(:post, user: #user) }
context "with valid attributes" do
subject(:create_action) { xhr :post, :create, post_id: post_instance.id, post_comment: attributes_for(:post_comment, post_id: post_instance.id, user: #user) }
it "saves the new task in the db" do
expect{ create_action }.to change{ PostComment.count }.by(1)
end
it "assigns instance variables" do
create_action
expect(assigns(:post)).to eq(post_instance)
#########How to test these two?
#expect(assigns(:post_comment)).to be_a_new(PostComment)
#expect(assigns(:post_comment.user)).to eq(#user)
expect(assigns(:post_comment_reply)).to be_a_new(PostCommentReply)
end
it "assigns all the instance variables"
it "responds with success" do
create_action
expect(response).to have_http_status(200)
end
end
context "with invalid attributes" do
subject(:create_action) { xhr :post, :create, post_id: post_instance.id, post_comment: attributes_for(:post_comment, post_id: post_instance.id, user: #user, body: "") }
it "doesn't save the new product in the db" do
expect{ create_action }.to_not change{ PostComment.count }
end
end
end
How to test these two?
expect(assigns(:post_comment)).to be_a_new(PostComment)
expect(assigns(:post_comment.user)).to eq(#user)
I believe you shoudl test not a new record, but a record of a class, and persisted record:
expect(assigns(:post_comment)).to be_a(PostComment)
expect(assigns(:post_comment)).to be_presisted
expect(assigns(:post_comment.user)).to eq(#user)
Excessive code.
#post_comment.save!
if #post_comment.save
You shall to keep only the single record of that, I believe it is enough save with exception:
#post_comment.save!
So other part code you can pick out of if block. Exception from save! you shall to trap with rescue_from.

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 testing templates being rendered

Im trying to test a condition where on successful signup a Success Template is rendered by the following controller code
def create
#user = User.new(params[:user])
if #user.save
render :template => "success"
else
flash[:notice] = "Oops Somethings not quite right! :("
render :action => "new"
end
end
I am using the following spec to test out this code
before(:each) do
#user = User.new
#user.attributes = valid_attributes
#params = valid_attributes
#user.stub!(:save).and_return(true)
end
def do_post
post :create
end
it "should create new user " do
count = User.count
do_post
user = User.new(#params)
user.save.should eql(true)
User.count.should eql(count + 1)
end
it "should render the success page on successful signup" do
do_post
#user.save
response.should render_template("success") if #user.save
end
But the example fails "it should render success page on successful signup" with this error message
1)
'UsersController handling POST /users should render the success page on successful signup' FAILED
expected "success", got "users/new.html.erb"
./spec/controllers/users_controller_spec.rb:67:
The success view is an template stored in the views/users/ without an action. Im guessing im making a very fundamental mistake and would like some help .
You are stubbing the #user variable in the test, but the controller will instantiate a new instance so the stub won't be in place.
It's not a good idea to use a stub in this case just to emulate a successful save call. Why don't you supply valid data instead and make sure the action is successful?
The following code is for RSpec > 2.1 and it uses the expect syntax.
before(:each) do
#params = valid_attributes
end
it "should create new user" do
#_before = User.count
post :create, :user => #params
expect(assigns(:user)).to_not be_new_record
expect(User.count).to eq(#_before + 1)
end
it "should render the success page on successful signup" do
post :create, :user => #params
expect(response).to be_successful
expect(response).to render_template("success")
end
Finally, change
render :template => "success"
to
render :action => "success"
For previous RSpec versions or if you have to use the should syntax, use
before(:each) do
#params = valid_attributes
end
it "should create new user" do
#_before = User.count
post :create, :user => #params
assigns(:user).should_not be_new_record
User.count.should == (#_before + 1)
end
it "should render the success page on successful signup" do
post :create, :user => #params
response.should be_successful
response.should render_template("success")
end

Resources