rspec controller spec matchers - ruby-on-rails

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.

Related

Testing requests that have redirects in RSpec

I'm trying to test a post request that has a redirect if successful:
class PostsController < ApplicationController
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post, notice: 'Post was successfully created.'
else
render :new
end
end
end
I was wondering if it were at all possible to test to see if I receive a 201 response code before the redirect. Here is how I currently have my code. It will err because the redirect happens first:
RSpec.describe 'Posts', type: :request do
describe 'POST #create' do
it 'has a 201 response code' do
post posts_path, params: { post: valid_attributes }
expect(response).to have_http_status(201)
end
end
end
You can check that a post was created and that a user was redirected, if params are valid. And if you have any validation in the Post model, it is good idea to test invalid params:
RSpec.describe 'PostsController', type: :request do
describe 'POST #create' do
context 'with valid params' do
it 'creates a new post' do
expect { post posts_path, params: { post: valid_attributes } }.to change(Post, :count).by(1)
expect(response).to redirect_to post_path(Post.last)
end
end
context 'with invalid params' do
it 'does not create a new post' do
expect { post posts_path, params: { post: invalid_attributes } }.not_to change(Post, :count)
expect(response).to have_http_status 200
end
end
end
end
You're response code will be a 302 since the post is being created successfully. In the example code you've been given you won't get a 201 back. You could check that you don't receive a 201
expect(response).to_not have_http_status(201).
Creating a new Post model doesn't return an HTTP status code. It creates a row in the database. If you want to check that a post was created you could check that the count of posts is 0 at the start of the test and 1 at the end.

wrong number of arguments (given 2, expected 0) in controller spec

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.

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 do I test association in rails controller with rspec?

I have an article model which has many comments and the comment belongs to one article. this is my create method for comments_controller.rb:
def create
#comment = Comment.new(comment_params)
#comment.article_id = params[:article_id]
#comment.save
redirect_to article_path(#comment.article)
end
I want to know what's the best approach to test this action with rspec. and I want to know testing methods for association in controller at all.
thank you experts.
You can access your comment object within your tests using assigns method:
describe CommentsController, type: :controller
let(:comment_params) {{ <correct params goes here>}}
let(:article_id) { (1..100).sample }
let(:create!) { post :create, comment: comment_params, article_id: article_id }
it "creates new comment" do
expect { create! }.to change { Comment.count }.by 1
end
it "assigns given comment to correct article"
create!
expect(assigns(:comment).article_id).to eq params[:article_id]
end
end
The above is just a guideline, you will need to modify it depending on your exact requirements.
I suggest this codes.
This code is using FactoryGirl.
factory_girl is a fixtures replacement with a straightforward definition syntax... https://github.com/thoughtbot/factory_girl
Please add gem 'factory_girl_rails' to Gemfile.
def create
#comment = Comment.new(comment_params)
#comment.article_id = params[:article_id]
if #comment.save
redirect_to article_path(#comment.article)
else
redirect_to root_path, notice: "Comment successfully created" # or you want to redirect path
end
end
describe "POST #create" do
let(:article_id) { (1..100).sample }
context 'when creation in' do
it 'creates a new comment' do
expect { post :create, comment: attributes_for(:comment), article_id: article_id }.to change {
Comment.count
}.from(0).to(1)
end
it 'returns same article_id' do
post :create, comment: attributes_for(:comment), article_id
expect(assigns(:comment).article_id).to eq(article_id)
end
end
context 'when successed in' do
before { post :create, comment: attributes_for(:comment), article_id }
it 'redirects article path' do
expect(response).to redirect_to(Comment.last.article)
end
end
context 'when unsuccessed in' do
before { post :create, comment: attributes_for(:comment), article_id }
it 'does not redirect article path' do
expect(response).to redirect_to(root_path)
end
end
end
uhh, I am not English native speaker. so If it's sentence is not natural, please modify sentences. :-(

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.

Resources