I’m teaching myself to write controller tests and am getting this getting this error:
ERROR["test_should_update_post", PostsControllerTest, 2015-10-11 12:12:31 -0400]
test_should_update_post#PostsControllerTest (1444579951.69s)
ActionController::UrlGenerationError: ActionController::UrlGenerationError: No route matches {:action=>"update", :controller=>"posts", :post=>{:title=>"My Post", :body=>"Updated Ipsum"}}
test/controllers/posts_controller_test.rb:51:in `block (2 levels) in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:50:in `block in <class:PostsControllerTest>’
This is my test:
test "should update post" do
assert_difference('Post.count') do
put :update, post: {title: 'My Post', body: 'Updated Ipsum'}
end
assert_redirected_to post_path(assigns(:post))
end
this is my yaml:
entry_one:
title: "Foobar"
body: "Ipsum This"
entry_two:
title: "Barfoo"
body: "This Ipsum"
and this is my controller:
def update
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post, notice: 'Event updated successfully'
else
render :edit
end
end
Can you point me towards the problem I need to solve?
I can tell from the error and the line count that it’s something to do with the lines:
assert_difference('Post.count') do and put :update, post: {title: 'My Post', body: 'Updated Ipsum’}
You need to pass an id to the update action:
put :update, id: <THE ID HERE>, post: {title: 'My Post', body: 'Updated Ipsum'}
According to your update action in your controller, you need to pass an id of the post in your params.
So, in your test, build your params hash like this:
let(:update_query_parameters) { { post: { title: 'My Post', body: 'Updated Ipsum' }, id: post.id } }
Then, use update_query_parameters to pass as params for your put :update method:
test "should update post" do
assert_difference('Post.count') do
put :update, update_query_parameters
end
assert_redirected_to post_path(assigns(:post))
end
Thanks to two the commenters above, I was able to understand the problem I needed to solve: That I need to pass an id in my update test.
I'd already done this in a similar edit test for the same app, i knew exactly what to try.
I'd previously used a setup method in my test to pass my yaml shared above into my tests:
def setup
#post = posts(:entry_one)
end
With this method I can pass #post.id into my update test and get it to pass as such:
test "should update post" do
assert_no_difference('Post.count') do
put :update, id: #post.id, post: {title: 'My Post', body: 'Updated Ipsum'}
end
assert_redirected_to post_path(assigns(:post))
end
Related
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.
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
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'}
I am building a simple blog app in order to learn BDD/TDD with RSpec and Factory Girl. Through this process, I continue to run into 'Failures' but I believe they have more to do with how I am using Factory Girl than anything.
As you'll see below, in order to get my specs to pass, I'm having a hard time keeping my test DRY - there must be something I am misunderstanding. You'll notice, I'm not using Factory Girl to it's full potential and at times, skipping it altogether. I find that I commonly run into problems when using functions such as get :create, get :show, or put :update within the spec.
I am currently stuck on the #PUT update spec that should simply test the assignment of the #post variable. I have tried multiple types of this spec that I found online, yet none seem to work - hence, is it Factory Girl? Maybe the specs I'm finding online are outdated Rspec versions?
I'm using:
Rspec 3.1.7
Rails 4.1.6
posts_controller_spec.rb
require 'rails_helper'
require 'shoulda-matchers'
RSpec.describe PostsController, :type => :controller do
describe "#GET index" do
it 'renders the index template' do
get :index
expect(response).to be_success
end
it "assigns all posts as #posts" do
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :index
expect(assigns(:posts)).to eq([post])
end
end
describe '#GET show' do
it 'assigns the request post to #post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :show, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#GET create' do
context 'with valid attributes' do
before :each do
post :create, post: attributes_for(:post)
end
it 'creates the post' do
expect(Post.count).to eq(1)
expect(flash[:notice]).to eq('Your post has been saved!')
end
it 'assigns a newly created post as #post' do
expect(assigns(:post)).to be_a(Post)
expect(assigns(:post)).to be_persisted
end
it 'redirects to the "show" action for the new post' do
expect(response).to redirect_to Post.first
end
end
context 'with invalid attributes' do
before :each do
post :create, post: attributes_for(:post, title: 'ha')
end
it 'fails to create a post' do
expect(Post.count).to_not eq(1)
expect(flash[:notice]).to eq('There was an error saving your post.')
end
it 'redirects to the "new" action' do
expect(response).to redirect_to new_post_path
end
end
end
describe '#GET edit' do
it 'assigns the request post to #post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :edit, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#PUT update' do
context 'with success' do
before :each do
post :create, post: attributes_for(:post)
end
it 'assigns the post to #post' do
put :update, id: post.id
expect(assigns(:post)).to eq(post)
end
end
end
end
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all.order('created_at DESC')
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
flash[:notice] = "Your post has been saved!"
redirect_to #post
else
flash[:notice] = "There was an error saving your post."
redirect_to new_post_path
end
end
def show
#post = Post.find(params[:id])
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
# if #post.update(params[:post].permit(:title, :body))
# flash[:notice] = "Your post is updated!"
# redirect_to #post
# else
# flash[:notice] = "There was an error updating your post."
# render :edit
# end
end
private
def post_params
params.require(:post).permit(:title, :body)
end
end
factories/post.rb
FactoryGirl.define do
factory :post do
title 'First title ever'
body 'Forage paleo aesthetic food truck. Bespoke gastropub pork belly, tattooed readymade chambray keffiyeh Truffaut ennui trust fund you probably haven\'t heard of them tousled.'
end
end
Current Failure:
Failures:
1) PostsController#PUT update with success assigns the post to #post
Failure/Error: put :update, id: post.id
ArgumentError:
wrong number of arguments (0 for 1+)
# ./spec/controllers/posts_controller_spec.rb:86:in `block (4 levels) in <top (required)>'
Finished in 0.19137 seconds (files took 1.17 seconds to load)
17 examples, 1 failure
You could definitely leverage factories here.
The factory you've created is actually fine too.
Instead of doing:
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
Do this: post = FactoryGirl.create(:post)
You can get ever more DRY if you do this:
# in spec/rails_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
This will allow you do this in your spec: post = create(:post)
Regarding your PUT test, try this from a previous SO answer:
describe '#PUT update' do
let(:attr) do
{ :title => 'new title', :content => 'new content' }
end
context 'with success' do
before :each do
#post = FactoryGirl.create(:post)
end
it 'assigns the post to #post' do
put :update, :id => #post.id, :post => attr
#post.reload
expect(assigns(:post)).to eq(post)
end
end
end
Edit:
Also, don't be afraid of moving things in to a before :each do if you need to. They are great at keeping things DRY
The immediate reason why your spec is failing is because you can only call on the controller once per test, and for update you're calling it twice: in the before-action, you are calling create... and then in the main part of the update test you are calling update... controller specs don't like that.
In order to get the existing spec working, you would need to replace the post :create, post: attributes_for(:post) line in the before-action with just creating a post or (as mentioned already) using factory girl to create a post - rather than trying to do it by calling the controller to do it.
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)