Can't fix ProductsController PUT update errors - ruby-on-rails

I have a problem with workshop app that i'm doing right now. I can't fix two last errors from tests. In my opinion application is working fine in browser. Maybe there is something wrong with tests? Any help would be appreciated.
Two errors:
1) ProductsController PUT update with valid params updates the requested product
Failure/Error: Unable to find matching line from backtrace
Exactly one instance should have received the following message(s) but didn't: update
2) ProductsController PUT update with invalid params re-renders the 'edit' template
Failure/Error: response.should render_template("edit")
expecting <"edit"> but rendering with <[]>
Test code:
require 'spec_helper'
describe ProductsController do
let(:category) { create(:category) }
let(:valid_attributes) { { "title" => "MyString", "category_id" => category.id, "price" => 5.59,
"description" => "Lorem ipsum dolor sit amet"} }
let(:valid_session) { {} }
describe "PUT update" do
let(:user) { build(:user) }
before do
sign_in user
controller.stub(:user_signed_in?).and_return(true)
controller.stub(:current_user).and_return(user)
controller.stub(:authenticate_user!).and_return(user)
end
describe "with valid params" do
it "updates the requested product" do
product = Product.create! valid_attributes
Product.any_instance.should_receive(:update).with({ "title" => "MyString" })
put :update, { id: product.to_param, product: { "title" => "MyString" }, category_id:
category.to_param }, valid_session
end
describe "with invalid params" do
it "re-renders the 'edit' template" do
product = Product.create! valid_attributes
Product.any_instance.stub(:save).and_return(false)
put :update, { id: product.to_param, product: { "title" => "invalid value" }, category_id:
category.to_param }, valid_session
response.should render_template("edit")
end
end
end
end
ProductsController#update code:
def update
if self.product.update(product_params)
redirect_to category_product_url(category, product), notice: 'Product was successfully
updated.'
else
render action: 'edit'
end
end

In general
expecting <"edit"> but rendering with <[]>
Typically this means that you are expecting a render (following a validation failure for example) and your controller perform a redirect (after a successful saving of your model)
In you code
You stub the save method here:
Product.any_instance.stub(:save).and_return(false)
But call an action that uses the update method
if self.product.update(product_params)
So the action succeed -> your controller redirects -> your 'edit' template is not rendered -> your spec fails
Your solution
Instead of stubbing save, you should stub valid?, it's the good practice in that case
Product.any_instance.stub(:valid?).and_return(false)

Related

Load file error on Rspec

I'm following along with a tutorial on testing with Rspec. Getting a syntax error when trying to run the test. Here's my test code:
require 'rails_helper'
RSpec.describe CommentsController, type: :controller do
describe "comments#create action" do
it "should allow admins to create comments on posts" do
post = FactoryGirl.create(:post)
admin = FactoryGirl.create(:admin)
sign_in admin
post :create, params: { post_id: post.id, comment: { message: 'awesome post' } }
expect(response).to redirect_to root_path
expect(post.comments.length).to eq 1
expect(post.comments.first.message).to eq "awesome gram"
end
it "should require an admin to be logged in to comment on a post" do
post = FactoryGirl.create(:post)
post :create, params: { post_id: post.id, comment: { message: 'awesome post' } }
expect(response).to redirect_to new_admin_session_path
end
it "should return http status code of not found if the post isn't found" do
admin = FactoryGirl.create(:admin)
sign_in admin
post :create, params: { post_id: 'SUNSHINE', comment: { message: 'awesome post'} }
expect(response).to have_http_status :not_found
end
end
end
Here's the controller:
class CommentsController < ApplicationController
before_action :authenticate_admin!, only: [:create]
def create
#post = Post.find_by_id(params[:post_id])
return render_not_found if #post.blank?
#post.comments.create(comment_params.merge(admin: current_admin))
redirect_to root_path
end
private
def render_not_found(status=:not_found)
render plain: "#{status.to_s.titleize} :(", status: status
end
def comment_params
params.require(:comment).permit(:message)
end
end
Here's the terminal output when running the test:
Terminal output
What's odd is that when I comment the lines that are producing the error, the tests run as intended with the last test passing. I've checked the test file along with similar posts that describe the same problem and it looks to me like the syntax is correct. New to Rails so pardon the rookie mistakes.
The problem is that you have a variable named post:
post = FactoryGirl.create(:post) # define `post` variable
post :create, params: ... # try to call `post` method
Therefore, on subsequent lines post will refer to the variable, not the post method.
Solution 1: Rename the variable
my_post = FactoryGirl.create(:post)
post :create, params: { post_id: my_post.id, ... }
Solution 2: Use self.post to access the method
post = FactoryGirl.create(:post)
self.post :create, params: { post_id: post.id, ... }
Replicating the issue in irb:
def foo
"bar"
end
foo = "wat"
#=> "wat"
foo :hello
# SyntaxError: (irb):63: syntax error, unexpected ':', expecting end-of-input
# foo :hello
# ^
I think it has to do with you defining a variable named post then trying to call the Rspec method post:
it "should require an admin to be logged in to comment on a post" do
post = FactoryGirl.create(:post)
post :create, params: { post_id: post.id, comment: { message: 'awesome post' } }
expect(response).to redirect_to new_admin_session_path
end
Try re-naming it:
to call the Rspec method post:
it "should require an admin to be logged in to comment on a post" do
message = FactoryGirl.create(:post)
post :create, params: { post_id: message.id, comment: { message: 'awesome post' } }
expect(response).to redirect_to new_admin_session_path
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.

Testing Create Action in Rails Controller

Testing Rails 4 Application with RSpec 3 throws an Argument Error (2 for 0) when running a test on posts#create.
My controller:
def create
#post = Post.new(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to(#post,
:notice => 'Post was successfully created.') }
format.json { render :json => #post,
:status => :created, :location => #post }
else
format.html { render :action => "new" }
format.json { render :json => #post.errors,
:status => :unprocessable_entity }
end
end
end
private
def post_params
params.require(:post).permit(:title, :body)
end
My Routes:
resources :posts
My Test:
describe 'POST posts#create' do
it 'allows admin to create a new post' do
sign_out user
sign_in admin
post :create, post: { title: 'Title', body: 'Body' }
expect(response).to have_http_status(:success)
end
end
The problem is on the post :create... line of the controller test. I'm unsure of why it is not running correctly. Solutions I have attempted:
1 post :create, post: title: 'Title', body: 'Body'
Thinking there was something wrong about my syntax. This however, throws an error.
2 post :create, { title: 'Title', body: 'Body' }
I thought that the post might be implicit since it is the name of the controller. No dice.
3 post :create, post:(post_params)
Again, I thought that because I have set up the usual permitted params, that they would be necessary. Not surprisingly, the test doesn't have post_params in its current scope.
4 post :create, post: { admin: { title: 'Title', body: 'Body' } }
I had before read that you have to pass in the user creating the post item. Incorrect.
5 post :create=> { title: 'Title', body: 'Body' }
I thought it might be the case that posting implicitly creates a post because that is the controller. However, it returns the argument error.
6 post :post=> { title: 'Title', body: 'Body' }
Finally, I thought that I have to post a new post a give the parameters without having to use the create action.
Edit - 7 post(:create, post: { title: 'Title', body: 'Body' })
After reviewing the API docs (http://api.rubyonrails.org/classes/ActionController/TestCase.html), I thought this would work but it's still the same argument error.
8
describe 'POST admin#create' do
it 'allows admin to create a new post' do
sign_in admin
expect{
post :create, post: {title: 'Title', body: 'Body' }
}.to change(Post, :count).by(1)
end
end
I attempted to use expect as a block thinking that it would pick up the arguments. Nope.
Compromise Solution
describe 'POST admin#create' do
it 'allows admin to create a new post' do
sign_in admin
expect{
Post.create({title: 'Title', body: 'Body' })
}.to change(Post, :count).by(1)
end
end
I think the name of the action being the name of the controller threw RSpec off. So, instead I just manually created a post. Probably not the recommended solution.
None of these worked. What am I missing?
Error Output
Failure/Error: post(:create, post: { title: 'Title', body: 'Body' })
ArgumentError:
wrong number of arguments (2 for 0)
# ./spec/controllers/posts_controller_spec.rb:101:in `block (3 levels) in <top (required)>'
I have also taken off all of my before_filters in order to see if that was the problem, however, it gives the same error.
Your syntax looks correct. It would look like this:
describe 'POST admin#create' do
it 'allows admin to create a new post' do
sign_out user
sign_in admin
post(:create, {post: {title: 'Title', body: 'Body'}})
expect(response).to have_http_status(:success)
end
end
...but the parens and outer curlys are optional.
The error is not coming from your test. The error occurs during the post to :create. It is coming from inside the controller or the model during the :create request.

Expected response to be a <redirect>, but was <200> when using get :edit

I have a problem with redirecting and testing it in rspec
I have a problem with test not passing when I use a get method, but are green for the same code base when I use a put method. I don't know how to work around this and need assistance in making tests pass.
I get a 200 HTTP status code, but I want to get confirmation on redirect, so that it could be tracked by rspec. What code basically needs to do is redirect logged in user who is not owner of a product to category_product_url(category, product) with a flash error: 'You are not allowed to edit this product.' when trying to edit product with get http method.
Used gems rspec-rails, devise and decent_exposure. Ruby 2.1.5 and Rails 4.1.8
Actual error message:
Failure/Error: expect(response).to redirect_to(category_product_url(category, product))
Expected response to be a <redirect>, but was <200>
Failure/Error: expect(controller.flash[:error]).to eq 'You are not allowed to edit this product.'
expected: "You are not allowed to edit this product."
got: nil
(compared using ==)
My spec
context 'another user is singed in' do
let(:user) { create(:user) }
let(:user2) { build(:user) }
let(:product) { Product.create! valid_attributes }
before do
sign_in user2
controller.stub(:user_signed_in?).and_return(true)
controller.stub(:current_user).and_return(user2)
controller.stub(:authenticate_user!).and_return(user2)
product.user = user
end
describe 'GET edit' do
describe 'with valid params' do
it 'redirects to product page' do
get :edit, { id: product.to_param, category_id: category.to_param }
expect(response).to redirect_to(category_product_url(category, product))
end
it 'renders error message' do
get :edit, { id: product.to_param, category_id: category.to_param }
expect(controller.flash[:error]).to eq 'You are not allowed to edit this product.'
end
end
end
My controller
before_action :authenticate_user!, only: [:new, :edit, :update, :destroy, :create]
expose(:category)
expose(:products)
expose(:product)
def edit
end
def update
if product.user == current_user
if self.product.update(product_params)
redirect_to category_product_url(category, product), notice: 'Product was successfully updated.'
else
render action: 'edit'
end
else
redirect_to category_product_url(category, product), flash: { error: 'You are not allowed to edit this product.' }
end
end
private
def product_params
params.require(:product).permit(:title, :description, :price, :category_id, :user_id)
end
What is strange, is that put method is working fine with the same update action. Following specs are passing
describe 'PUT update' do
describe 'with valid params' do
it 'redirects to product page' do
put :update, { id: product.to_param, product: { 'title' => 'MyString' }, category_id: category.to_param }
expect(response).to redirect_to(category_product_url(category, product))
end
it 'does not update product' do
put :update, { id: product.to_param, product: { 'title' => 'MyNewString' }, category_id: category.to_param }
expect(controller.product.title).to_not eq 'MyNewString'
end
it 'renders error message' do
put :update, { id: product.to_param, product: { 'title' => 'MyString' }, category_id: category.to_param }
expect(controller.flash[:error]).to eq 'You are not allowed to edit this product.'
end
end
end
The way how decent_exposure and devise work, you need to first invoke a before_action method passing there a private method of your choice.
For this particular example your controller should contain this
before_action :author!, only: [:edit, :update]
and a private method filter
def author!
unless self.product.user == current_user
redirect_to category_product_url(category, product),
flash: { error: 'You are not allowed to edit this product.' }
end
end
This way you get your get http requests and put http requests pass the specs.
Try this
expect(:get => "/products/1/edit").to route_to(:controller => "controller_name", :action => "action_name")
Hope this will work.

Resources