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
Related
I am learning how to test controllers in Rails. I have this action in my Posts Controller:
def update
#post = Post.new(post_params)
if #post.save
redirect_to posts_path
flash[:success] = "Your post has been updated"
else
render 'edit'
end
end
Pretty basic update action. I want to test it. This is the test I have right now:
require 'rails_helper'
RSpec.describe PostsController, type: :controller do
let!(:test_post) { Post.create(title: "testing", body: "testing") }
describe "PUT update" do
context "when valid" do
it "updates post" do
patch :update, id: test_post, post: {title: 'other', body: 'other'}
test_post.reload
expect(test_post.title).to eq('other')
end
end
end
end
This test does not pass. This is the error I get from RSpec:
1) PostsController PUT update when valid updates post
Failure/Error: expect(test_post.title).to eq('other')
expected: "other"
got: "testing"
(compared using ==)
I would appreciate some guidance. Thanks!
In your update action, you're creating a new Post, not updating an existing Post:
def update
#post = Post.new(post_params) <= here
if #post.save
redirect_to posts_path
flash[:success] = "Your post has been updated"
else
render 'edit'
end
end
You need to find your existing Post record, then update it. Which might look something more like:
def update
#post = Post.find_by(id: params[:id]) <= might need to be different depending on how you have structured your params
if #post.update_attributes(post_params)
redirect_to posts_path
flash[:success] = "Your post has been updated"
else
render 'edit'
end
end
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.
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 user model (agents) in my app who can create posts. They can do this from their dashboard. I also show all their existing posts within their dashboard. The problem comes when trying to create a new post with no content which I'm testing to make sure it renders an error. Creating a post with content works but creating one with out keeps triggering an error which says my #posts instance variable is nil. Not sure if I missed something?
Dashboard view:
.container
.column.span9
- if current_agent
= render 'home/shared/agent_post_panel'
= render 'home/shared/agent_dashboard_tabs'
agent_dashboard_tabs:
.tabs-container
#posts
.content
- if #posts.any? //This is where the error is triggered
= render partial: 'shared/post', collection: #posts
Controller for Dashboard:
class HomeController < ApplicationController
before_filter :authenticate!, only: [:dashboard]
def index
end
def dashboard
if current_agent
#post = current_agent.posts.build
#posts = current_agent.posts
end
end
end
My post controller:
class PostsController < ApplicationController
before_filter :authenticate_agent!
def create
#post = current_agent.posts.build(params[:post])
if #post.save
flash[:notice] = "Post created!"
redirect_to dashboard_path
else
flash[:error] = "Post not created"
render 'home/dashboard'
end
end
end
Tests:
feature 'Creating posts' do
let(:agent) { FactoryGirl.create(:agent) }
before do
sign_in_as!(agent)
visit dashboard_path
end
scenario "creating a post with valid content" do
fill_in 'post_content', :with => 'I love donuts'
expect { click_button "Post" }.to change(Post, :count).by(1)
end
scenario "creating a post with invalid content" do
expect { click_button "Post" }.not_to change(Post, :count)
click_button "Post"
page.should have_content("Post not created")
page.should have_content("can't be blank")
end
You are rendering home/dashboard from within posts#create when the post has errors, but you are not setting #posts variable.
You should add this before render 'home/dashboard'
#posts = current_agent.posts
Use redirect_to dashboard_path instead of render 'home/dashboard'.
and you can keep flash message on dashboard by calling keep method of flash in home#dashboard.
flash.keep
If I understand your question correctly, You are having an issue in the loading the page (HomeController#index)
As I can see prior to load your index action, you check if the user is logged in or not,
make sure your application flow goes into
if current_agent
#make sure you application flow comes here
#post = current_agent.posts.build
#posts = current_agent.posts
end
If not you might want to slightly modify this flow as (if this fits your requirment)
if current_agent
#post = current_agent.posts.build
#posts = current_agent.posts
else
#post = Post.new
#posts = []
end
and finally its always good to use try, when you are not sure about the object you get
if #posts.try(:any?)
I have written this controller code in Ruby on Rails
class PostsController < ApplicationController
before_filter :authenticate_user!
def index
#posts = Post.all(:order => "created_at DESC")
respond_to do |format|
format.html
end
end
def create
#post = Post.create(:message => params[:message])
respond_to do |format|
if #post.save
format.html { redirect_to posts_path }
format.js
else
flash[:notice] = "Message failed to save."
format.html { redirect_to posts_path }
end
end
end
end
and corresponding to this I have written the following test case :-
require 'spec_helper'
describe PostsController do
describe "GET 'index'" do
it "returns http success" do
get 'index'
response.should be_success
end
end
describe "#create" do
it "creates a successful mesaage post" do
#post = Post.create(message: "Message")
#post.should be_an_instance_of Post
end
end
end
I am getting failures on both. Please take a look on the code and help me figure out.
I suspect you are not logged in since you are using Devise?
Maybe you need to include the devise testhelpers:
describe PostsController do
include Devise::TestHelpers
before(:each) do
#user = User.create(...)
sign_in #user
end
#assertions go here
end
As Tigraine states, it appears as though you probably are not logged in (with Devise) when the tests get executed. However, showing the failures would help in narrowing down the problem further.
On top of that, the second test isn't really an integration test and I would probably prefer something like the following to test the same condition. There are two types of test you could do:
# inside 'describe "#create"'
let(:valid_params) { {'post' => {'title' => 'Test Post'} }
it 'creates a new Post' do
expect {
post :create, valid_params
}.to change(Post, :count).by(1)
end
# and / or
it 'assigns a new Post' do
post :create, valid_params
assigns(:post).should be_a(Post)
assigns(:post).should be_persisted
end
Don't forget to add this line into your spec_helper.rb
require "devise/test_helpers"
include Devise::TestHelpers
Nevertheless, here is link for Devise wiki - How to test Controllers where you can find more info about this approach. I recommend writing the before method without (:each), what I remember it sometimes causes problems.
before do
#user = FactoryGirl.create(:user)
sign_in #user
end
Can always use:
puts response.inspect
To see how your response looks like.