I'm upgrading a Rails site from 4.0 to 4.1.12 and quite a few of my Rspec controller tests are now broken. For example this test broke with the upgrade:
it "update action should render edit template" do
#user = create(:user)
#user.name = "" # model requires name
#controller.stubs(:current_user).returns(#user)
put :update, id: #user
expect(response).to render_template(:edit)
end
I'm getting "No route matches {:action=>"show", :controller=>"accounts", :id=>nil} missing required keys: [:id]". It seems like the update_attributes method is ignoring my model validations all of a sudden.
Controller code:
def update
#user = current_user
if #user.update_attributes(params[:user])
redirect_to user_path(#user), :notice => "Your profile has been updated."
else
render :action => 'edit'
end
end
routes.rb
resources :users do
member do
get 'accounting'
end
collection do
post 'send_password'
end
end
I'm sure I've missed something in the upgrade process but I don't see anything in the docs that's telling me what that is.
So just to put down what I would do - rather than changing the #user object in the test you need to send in the changes you want to make to the user object as params of the request. This way the controller would receive it in the params[] hash - otherwise you aren't really testing what the controller method is doing.
it "with invalid data; update action should render edit template" do
#user = create(:user)
#controller.stubs(:current_user).returns(#user)
patch :update, id: #user, user: { name: '' }
expect(response).to render_template(:edit)
end
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.
Here's my Code for controller
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
and my code for testing
test "should get create post" do
assert_difference('Post.count') do
post :create, post: {title: #post.title, content: #post.content}
end
assert_redirected_to post_path(assigns{:post})
end
The error that I'm getting is
ArgumentError: comparison of Array with Array failed
test/controllers/posts_controller_test.rb:22:in `block in '
if i remove (assigns{:post}) and try the test
i get this error
ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"posts"} missing required keys: [:id]
Also how to test the if fails render 'new' part in controller.
Thanks
In the following line
assert_redirected_to post_path(assigns{:post})
use parenthesis instead of curly braces. The code should look like this:
assert_redirected_to post_path(assigns(:post))
#post is not instantiated anywhere in the test. You need to pass in params like so
post :create, post: {title: 'title', content: 'content'}
In a little Rspec test like this:
describe 'POST create' do
context 'with valid attributes' do
#document = FactoryGirl.attributes_for(:document_with_publication)
it 'creates a new document' do
sign_in admin
post :create, document: #document
expect(response).to change(Document, :count).by(1)
end
end
end
I get this error:
DocumentsController user is an administrator POST create with valid attributes creates a new document
Failure/Error: post :create, document: #document
ActionController::RoutingError:
No route matches {:controller=>"documents", :action=>"new", :template=>nil}
Why do I seem to be hitting my :new action and not :create? My routes look like this:
resources :documents, except: [:new, :show]
get 'documents/:template/new', to: 'documents#new', as: :new_templated_document
Thanks. Relevant controller code is here:
def new
#document = current_user.documents.new
#document.template = Template.find(params[:template])
#template_resources = TemplateResources.new(#document, current_user)
end
def create
#document = current_user.documents.new(params[:document])
if #document.save
second_stage_processing
else
redirect_to new_templated_document_path(#document.template), flash:
{ error: 'The document you tried to create was invalid: ' \
"#{#document.errors.full_messages.map { |msg| msg }.join}" }
end
end
I think it is hitting the create action and the #document.save is failing which causes the redirect to redirect_to new_templated_document_path(#document.template) to be called. Since :template is required for that route, but is nil, the route is failing.
I have an Admin model which can manage Organizations.
I have an AdminController with a simple index action and a child Admin::OrganizationsController controller which I'm trying to test.
The test for the canonical show action on this child controller passes without errors:
describe "GET show" do
it "assigns the requested organization as #organization" do
org = FactoryGirl.create(:organization)
get :show, id: org.id # <---- this works
expect(assigns(:organization)).to eq(org)
end
end
but when I try to test the destroy action, I get an error I'm not able to understand (hence resolve):
describe "DELETE destroy" do
it "destroys the requested organization" do
org = FactoryGirl.create(:organization)
delete :destroy, id: org.id # <---- (I tried to use org.id.to_param, unsuccessfully)
# ...rest of the test
end
end
with error:
Failure/Error: expect { delete :destroy, id: org.id }.to change(Organization, :count).by(-1)
NameError:
undefined local variable or method `organizations_url' for #<Admin::OrganizationsController:0x007fefe1622248>
I suspect this has to do with my controller being "nested" (it needs something like admin_organizations_url I guess).
Any help?
(additional side infos: Rails 4.0.1, rspec 3.0.0.beta1)
"Inspired" by CDub's comment, I took a look at the destroy action in Admin::OrganizationController, which looked like this:
def destroy
#organization.destroy
respond_to do |format|
format.html { redirect_to organizations_url } # <--- has to be admin_organizaions_url
format.json { head :no_content }
end
end
I didn't pay attention to the respond_to block at all.
I'm trying to create a very simple blog using Rails, for my own education. It's the first Rails app I've ever created other than from working through tutorials.
So far I just have a very simple model where each post has only a string for the title and a string for the content. Everything works fine and as expected in the browser, but I can't get the tests to pass.
Here's are the failing test in my Rspec code (spec/requests/post_spec.rb):
require 'spec_helper'
describe "Posts" do
.
.
.
describe "viewing a single post" do
#post = Post.create(title: "The title", content: "The content")
before { visit post_path(#post) }
it { should have_selector('title', text: #post.title) }
it { should have_selector('h1', text: #post.title) }
it { should have_selector('div.post', text: #post.content) }
end
end
This gives me the same error message for all 3:
Failure/Error: before { visit post_path(#post) }
ActionController::RoutingError:
No route matches {:action=>"show", :controller=>"posts", :id=>nil}
So it seems to me the problem is that the line #post = Post.create(...) is creating a post without an id, or else it's not saving the post to the test database correctly. How do I fix this? And am I going about this the right way in the first place, or is there a better way I could be creating the test post/testing the page?
This is only a problem in testing. When I view a single post in the browser everything looks fine. The Posts controller is: (I have edited this since posting the original question)
class PostsController < ApplicationController
def new
#post = Post.new
end
def create
#post = Post.new(params[:post])
if #post.save
redirect_to posts_path, :notice => "Post successfully created!"
end
end
def index
end
def show
#post = Post.find(params[:id])
end
end
And here's the Post model in its entirety:
class Post < ActiveRecord::Base
attr_accessible :content, :title
validates :content, presence: true
validates :title, presence: true
end
config/routes:
Blog::Application.routes.draw do
resources :posts
root to: 'posts#index'
end
app/views/posts/show.html.erb:
<% provide(:title, #post.title) %>
<h1><%= #post.title %></h1>
<div class="post"><%= #post.content %></div>
Your instance variable needs to go into the before block. (the test is trying to goto /posts/:id/show and params[:id] in this case is nil as #post hasnt been created)
try:
before do
#post = Post.create(title: "The title", content: "The content")
visit post_path(#post)
end
Ok so it seems your new and create actions are empty?? Try
def new
#post =Post.new
end
def create
#post = Post.new(params[:post])
if #post.save
redirect_to posts_path, :notice => " Post successfully created."
end
end
and then your view for new needs to have a form_for #post
You cant create a new post without this and only when this is successful your posts will e assigned ids
Do you need to put your 'before' stuff outside the test? This works for me:
require 'spec_helper'
describe "Posts" do
before(:each) do
#post = Post.create(title: "The title", content: "The content")
end
describe "viewing a single post" do
it "should show the post details" do
get post_path(#post)
response.status.should be(200)
# do other tests here ..
end
end
end