Testing requests that have redirects in RSpec - ruby-on-rails

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.

Related

How to get rspec coverage for the failure of .save

In my controller I have the following code:
def create
#post = Post.new(post_params)
if #post.save
flash[:notice] = "#{#post.title} was successfully created."
redirect_to post_path
else
flash[:alert] = #post.errors.full_messages
render :new
end
end
I've managed to write rspec code to cover the true statement within a feature spec, however I am struggling for the false statement. So far this is the rspec I have come up with to solve my problem (placed in posts_controller_spec.rb):
it 'should return false and render the new template' do
allow_any_instance_of(Post).to receive(:valid?).and_return(false)
expect(response).to render_template(:new)
end
Unfortunately, I am met with the following error:
Failures:
1) PostsController create should return
Failure/Error: expect(response).to render_template(:new)
expecting <"new"> but rendering with <[]>
I've tried looking at other questions on stackoverflow as well as other suggestions online, but can't seem to figure it out.
Any help would be much appreciated :)
In your test, you never call the controller.
it 'renders the new template' do
allow_any_instance_of(Post).to receive(:valid?).and_return(false)
post :create, params: {...}
expect(response).to render_template(:new)
end
It would be more direct to do receive(:save).and_return(false).
However, there's no need for a mock here. Send invalid Post parameters.
context 'when the Post params are invalid' do
let(:params) do
{
post: { something: "invalid" }
}
end
it 'does not make a Post, renders the new template, and flashes an error' do
expect {
post :create, params: params
}.to change {
Post.count
}.by(0)
expect(response).to render_template(:new)
expect(flash[:alert]).not_to be_empty
end
end

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

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.

Rails how to fix malformed request (error code 400)?

I am running RSpec for api, specifically to create posts (I am also working on :update and :destroy, but those two are running fine. I am having trouble with :create)
Here are my RSpec:
describe "POST create" do
before { post :create, topic_id: my_topic.id, post: {title: #new_post.title, body: #new_post.body} }
it "returns http success" do
expect(response).to have_http_status(:success)
end
it "returns json content type" do
expect(response.content_type).to eq 'application/json'
end
it "creates a topic with the correct attributes" do
hashed_json = JSON.parse(response.body)
expect(hashed_json["title"]).to eq(#new_post.title)
expect(hashed_json["body"]).to eq(#new_post.body)
end
end
And here is my create
def create
post = Post.new(post_params)
if post.valid?
post.save!
render json: post.to_json, status: 201
else
render json: {error: "Post is invalid", status: 400}, status: 400
end
end
Here is the error code that I keep getting:
.........F.F....
Failures:
1) Api::V1::PostsController authenticated and authorized users POST create returns http success
Failure/Error: expect(response).to have_http_status(:success)
expected the response to have a success status code (2xx) but it was 400
# ./spec/api/v1/controllers/posts_controller_spec.rb:78:in `block (4 levels) in <top (required)>'
I am really not sure what is wrong with the code. The routes work fine. How can I make the tests pass?
For a better solution tha #spickermann suggested, change post to #post in your #create action and add the code below to your specs and work from there. I bet you have to do something like #post.user = current_user in your controller.
it "#post is valid and have no errors" do
expect(assigns[:post]).to be_valid
expect(assigns[:post].errors).to be_empty
end

'undefined method' post with Rails and Rspec

Got stuck with:
' undefined method `post' for #<Class:0x000001058c0f68> (NoMethodError)'
on testing controller create action.
I'm using Rails 4, rpsec, and Factory Girl
Controller:
def create
#post = Post.new(post_params)
#post.user_id = current_user.id
if #post.save
flash[:success] = "Yay! Post created!"
redirect_to root_path
else
# flash[:error] = #post.errors.full_messages
render 'new'
end
end
Test:
describe '#create' do
post 'create', FactoryGirl.attributes_for(:post, user: #user)
response.should be_successful
end
I think post method is accessible inside it method block:
describe 'create' do
it 'should be successful' do
post :create, FactoryGirl.attributes_for(:post, user: #user)
response.should be_success
end
end
BTW I think you need to test for redirect, not success status.
Sorry for being off-topic but I just want to give you some advice.
Consider following best practices and use RSpec's expect syntax instead of should. Read more about why the should syntax is a bad idea here: http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
This is how I would rewrite your example:
describe 'create' do
it 'responds with 201' do
post :create, attributes_for(:post, user: #user)
expect(response.status).to eq(201)
end
end
In the example I'm using FactoryGirl's short syntax method attributes_for instead of FactoryGirl.attributes_for, it saves a few bytes. Here's how to make the short methods available (in spec/test_helper.rb):
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
I'm testing for the status code 201 which Rails will return by default for a successful create action (redirect should be 3xx).This makes the test more specific.
Hope it's any help for writing better specs.
The issue comes from the fact that post should be used inside an it statement. I usually test my controllers like this:
describe 'POST "create"' do
let(:user) { User.new }
let(:params) { FactoryGirl.attributes_for(:post, user: user) }
let(:action) { post :create, params }
let!(:post) { Post.new }
before do
Post.should_receive(:new).and_return(post)
end
context 'on success' do
before do
post.should_receive(:save).and_return(true)
end
it 'renders success' do
action
expect(response).to be_success
end
it 'redirects' do
action
expect(response).to be_redirected
end
it 'sets flash message' do
action
expect(flash[:success]).to_not be_empty
end
end
context 'on failure' do
before do
post.should_receive(:save).and_return(false)
end
it 'renders new' do
action
expect(response).to render_template(:new)
end
end
end

Resources