I'm a newbie in RSpec, I have this controller in my ruby on rails code
def create
#article = current_user.articles.build params[:article]
if #article.save
redirect_to articles_path, :notice => 'Article saved successfully!'
else
render :new
end
end
How would you test this action in RSpec ?
Thank you
describe "POST 'create'" do
let(:article) { mock_model(Article) }
before(:each) do
controller.stub_chain(:current_user,:articles,:build) { article }
end
context "success" do
before(:each) do
article.should_receive(:save).and_return(true)
post :create
end
it "sets flash[:notice]" do
flash[:notice].should == "Article saved successfully!"
end
it "redirects to articles_path" do
response.should redirect_to(articles_path)
end
end
context "failure" do
before(:each) do
article.should_receive(:save).and_return(false)
post :create
end
it "assigns #article" do
assigns(:article).should == article
end
it "renders new" do
response.should render_template('new')
end
end
end
Related
I started to learn Rspec one day ago. When writing test for my articles controller, I got error at create new article. Here is my controller:
def create
Article.transaction do
begin
#article = Article.new(article_params)
respond_to do |format|
if #article.save
view_context.create_sitemap
flash[:show_alert] = true
format.html { redirect_to edit_admin_article_path(#article), notice: 'Created sucessfull' }
else
format.html { render :new }
format.json { render json: #article.errors,notice: "Unprocessable entity" }#may need a helper to handle exception
end
end
rescue Exception => e
raise ActiveRecord::Rollback
respond_to do |format|
flash[:show_alert] = true
format.html { redirect_to new_admin_article_path, notice: 'Create failed'}
end
end
end
end
here is my test:
describe "POST #create" do
context "with valid attributes" do
it "creates a new article" do
expect{
post :create, params: { article: FactoryGirl.attributes_for(:article) }
}.to change(Article, :count).by(1)
end
it "redirects to the index page" do
post :create, params: { article: FactoryGirl.attributes_for(:article) }
expect(response).to redirect_to admin_articles_path
end
end
context "with invalid attributes" do
it "does not save the new article" do
expect{
post :create, params: { article: FactoryGirl.attributes_for(:article) }
}.to_not change(Article, :count).by(1)
end
it "re-renders the :new template" do
post :create, params: { article: FactoryGirl.attributes_for(:article) }
expect(response).to render_template :new
end
end
end
And here is the log:
3) Admin::ArticlesController POST #create with valid attributes creates a new article
Failure/Error:
expect{
post :create, params: { article: FactoryGirl.attributes_for(:article) }
}.to change(Article, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/admin/articles_controller_spec.rb:29:in `block (4 levels) in <top (required)>'
I spent time to search for the same issue but, all of them didn't solve my error. My problem is I cannot find out where the error come from. Any help is appreciated.
I've got a controller test set running where three of the tests succeed and three fail with the same type of error.
For the tests for the edit, update, and destroy actions, I get the associated error saying No route matches {:controller=>"accounts", action=>"edit"}
accounts_controller_spec.rb
describe AccountsController do
before(:each) do
#account_code = FactoryGirl.create(:account)
end
describe "GET 'index'" do
it "returns http success" do
get 'index'
expect(response).to be_success
end
end
describe "GET 'new'" do
it "returns http success" do
get 'new'
expect(response).to be_success
end
end
describe "POST 'create'" do
it "returns http success" do
post 'create'
expect(response).to be_success
end
end
describe "GET 'edit'" do
it "returns http success" do
get 'edit'
expect(response).to be_success
end
end
describe "POST 'update'" do
it "returns http success" do
post 'update'
expect(response).to be_success
end
end
describe "DELETE 'destroy'" do
it "returns http success" do
post 'destroy'
expect(response).to be_success
end
end
end
accounts_controller.rb
class AccountsController < ApplicationController
load_and_authorize_resource
def index
end
def new
end
def create
if #account.save
flash[:success] = "Account created"
redirect_to :action => :index
else
render 'new'
end
end
def update
if #account.update_attributes(params[:account])
flash[:success] = "Account Updated"
redirect_to :action => :index
else
render 'edit'
end
end
def edit
end
def destroy
#account.destroy
flash[:success] = "Account Deleted"
redirect_to accounts_path
end
end
routes.rb
resources :account_codes
I see two errors here
you do not use the correct verbs for destroy and update, you should use 'delete' for destroy and 'put' for update
you do not provide an 'id' for these actions, you should use get :edit, id: 1 , put :update, id: 1 ...
try running rake routes to see your exact routes
PS: I think you would get the same error for a show action as well. If you do not need that action, pass it in as except: :show in your resources on routes.rb
I have a bit of a confusing rSpec issue - depending how I write my code, either the tests that describe the 'failing' specs fail or the tests that describe the 'successful' specs fail.
Here are the tests for the create action:
describe "POST 'create'" do
describe "failure" do
before(:each) do
#attr = {name: "", type_of_group: ""}
#student_attr = [{name: "Joe", gender: "Male"}, {name: "sally twotrees", gender: "Female"}]
#create = post :create, student_group: #attr, student: #student_attr
end
it "should have the right title" do
#create
response.should have_selector('title', :content => "Create a new group" )
end
it "should render the 'new' page" do
#create
response.should render_template('new')
end
it "should not create a user" do
lambda do
post :create, student_group: #attr
end.should_not change {#user.student_groups.count}
end
it "should flash an error message" do
#create
flash[:error].should =~ /please/i
end
end
describe "success" do
before(:each) do
#attr = FactoryGirl.attributes_for(:student_group)
# #student_attr = {name: "test", gender: "Male"}
end
it "should create a student_group" do
lambda do
post :create, student_group: #attr
end.should change {#user.student_groups.count}.by(1)
end
it "should create students" # do
# lambda do
# post :create, student_group: #attr, student: #student_attr
# end.should change {#student_groups.students.count}.by(1)
# end
it "should flash a success message" do
post :create, student_group: #attr
flash[:success].should =~ /has been added/i
end
it "should redirect" do
post :create, student_group_id: #group, student_group: #attr
response.should be_redirect
end
end
end
All of the 'failure' tests fail with this error:
Failure/Error: #create = post :create, student_group: #attr, student: #student_attr
ActionView::Template::Error:
`#student_group[students_attributes]' is not allowed as an instance variable name
if I write the code in my controller this way:
def create
#params = params[:student_group][:students_attributes]
#student_group = #user.student_groups.build(params[:student_group])
if #student_group.save
### RE: 'defensive coding' https://stackoverflow.com/questions/14502508/undefined-method-for-nilnilclass-when-pushing-values-to-an-array
if #params.present?
### https://stackoverflow.com/questions/11355820/rails-3-2-iterate-through-an-array
#params.each do |student|
#student_group.students.create(name:"#{student[:name]}", gender: "#{student[:gender]}")
end
end
# new subject path
redirect_to class_path(#student_group), flash: { success: "#{#student_group.name} has been added successfully" }
else
#title = "Create a new group"
flash.now[:error] = "Something's gone wrong. Please try again!"
render 'new'
end
end
and all of the 'success' tests fail if the controller code is written like this:
def create
#params = params[:student_group][:students_attributes]
#student_group = #user.student_groups.build(params[:student_group])
### http://railsforum.com/viewtopic.php?pid=40056#p40056
if #params.present?
#student = Student.new
else
#student = #student_group.students.build(#params)
end
if #student_group.save
### RE: 'defensive coding' https://stackoverflow.com/questions/14502508/undefined-method-for-nilnilclass-when-pushing-values-to-an-array
if #params.present?
### https://stackoverflow.com/questions/11355820/rails-3-2-iterate-through-an-array
#params.each do |student|
#student_group.students.create(name:"#{student[:name]}", gender: "#{student[:gender]}")
end
end
# new subject path
redirect_to class_path(#student_group), flash: { success: "#{#student_group.name} has been added successfully" }
else
#title = "Create a new group"
flash.now[:error] = "Something's gone wrong. Please try again!"
render 'new'
end
end
the form code is here: https://stackoverflow.com/a/17591802/2128691
from the above code it seems that your controller code is really messed up. In case of nested attributes, u just have to save the parent object. the child objects get saved automatically if they are valid. Also u dont need to assign the params the some instance object. they should be used directly. a simple example of nested attributes can be
User
has_many :comments
accepts_nested_attributes_for :comments
Comment
belongs_to :user
ur controller code should be as
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = 'success'
redirect_to some_path and return
end
render 'new'
end
the rspec controller test case can be as
it "should create a user with comments if valid data is provided" do
post :create, "user"=>{"name"=>"Prasad", "comments_attributes"=>{"0"=>{"comment"=>"first comment"}, "1"=>{"comment"=>"second comment"}}, "commit"=>"Save"
user = assigns[:user] #assigns lets u access the instance variable from the controller in the spec
user.should be_valid
user.comments.count.should == 2 #check that all the child models are saved
user.name.should == "Prasad"
user.comments.first.comment.should == 'first comment'
user.comments.last.comment.should == 'second comment'
response.should be_redirect(some_path) #since u redirected in the code
end
seriously, u need to go through rails guides.
I ended up using this code:
def create
#student_group = #user.student_groups.new(params[:student_group])
#params = params[:student_group][:students_attributes]
#student_group = #user.student_groups.build(params[:student_group])
if #student_group.save
### RE: 'defensive coding' http://stackoverflow.com/questions/14502508/undefined-method-for-nilnilclass-when-pushing-values-to-an-array
if #params.present?
### http://stackoverflow.com/questions/11355820/rails-3-2-iterate-through-an-array
#params.each do |student|
#student_group.students.create(name:"#{student[:name]}", gender: "#{student[:gender]}")
end
end
redirect_to new_student_group_subject_path(#student_group), flash: { success: "#{#student_group.name} has been added successfully. Next, add the subjects for this group" }
else
### http://railsforum.com/viewtopic.php?pid=40056#p40056
#student = #student_group.students.build
#title = "Create a new group"
flash.now[:error] = "Something's gone wrong. Please try again!"
render 'new'
end
end
I'm trying to write a Rspec test for my nested autolinks_controller. However, the redirect after my create action is broken. After successfully creating an autolink I want to be redirected to that autolink within a particular website (hence, website_autolink_path). My controller spec looks like this:
describe "POST create when params[:website_id] are present" do
before(:each) do
#website = create(:website)
#autolink = attributes_for(:website_autolink, website_id: #website.id)
end
context "with valid attributes and params[:website_id] are present" do
it "saved the autolink in the database" do
expect{
post :create, website_id: #website, autolink: attributes_for(:website_autolink)
}.to change(Autolink, :count).by(1)
end
it "redirects to the 'index' page" do
post :create, website_autolink: #autolink, website_id: #website
response.should redirect_to website_autolink_path
end
end
end
This line is not working:
response.should redirect_to website_autolink_path
Giving me the error message:
ActionController::RoutingError:
No route matches {:action=>"show", :controller=>"autolinks"}
My factories look like this:
Autolink:
FactoryGirl.define do
factory :website_autolink do
name "MyName"
url "http://www.myurl.nl"
association :website
end
end
Website:
FactoryGirl.define do
factory :website do
name "Test"
domain "http://www.test.nl"
end
end
My AutolinkController:
def create
if params[:website_id].present?
#website = Website.find(params[:website_id])
#autolink = #website.autolinks.create(params[:autolink])
else
#autolink = Autolink.new(params[:autolink])
end
respond_to do |format|
if #autolink.save
if params[:website_id].present?
format.html { redirect_to [#website, #autolink], notice: "Autolink is met succes aangemaakt." }
else
format.html { redirect_to autolinks_path, notice: "Autolink is met succes aangemaakt." }
end
format.json { head :no_content }
else
format.html { render action: "new" }
format.json { render json: #autolink.errors, status: :unprocessable_entity }
end
end
end
Within my controller, the following line is the one I want to simulate using Rspec:
format.html { redirect_to [#website, #autolink], notice: "Autolink is met succes aangemaakt." }
In my localhost it's all working, but writing the actual test for this nested route troubles me.
I just found a solution for my problem. My controller spec for the create action now looks like this:
describe "POST create when params[:website_id] are present" do
context "with valid attributes and params[:website_id] are present" do
before(:each) do
#website = create(:website)
#autolink = attributes_for(:website_autolink, website: #website)
end
it "saved the autolink in the database" do
expect{
post :create, autolink: #autolink, website_id: #website.id
}.to change(Autolink, :count).by(1)
end
it "redirects to the 'index' page" do
post :create, autolink: #autolink, website_id: #website.id
response.should redirect_to website_autolink_path(#website, assigns(:autolink))
end
end
end
I just had to assign my autolink in order to redirect_to the nested path. Without it, the id of my autolink couldn't be found.
I am starting a project and i would like to be able to test everything :)
And i have some problems with CanCan and devise.
For exemple, I have a controller Contacts. Everybody can view and everybody (excepts banned people) can create contact.
#app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
load_and_authorize_resource
def index
#contact = Contact.new
end
def create
#contact = Contact.new(params[:contact])
if #contact.save
respond_to do |f|
f.html { redirect_to root_path, :notice => 'Thanks'}
end
else
respond_to do |f|
f.html { render :action => :index }
end
end
end
end
The code work, but I don't how to test the controller.
I tried this. This works if I comment the load_and_authorize_resource line.
#spec/controllers/contacts_controller_spec.rb
require 'spec_helper'
describe ContactsController do
def mock_contact(stubs={})
(#mock_ak_config ||= mock_model(Contact).as_null_object).tap do |contact|
contact.stub(stubs) unless stubs.empty?
end
end
before (:each) do
# #user = Factory.create(:user)
# sign_in #user
# #ability = Ability.new(#user)
#ability = Object.new
#ability.extend(CanCan::Ability)
#controller.stubs(:current_ability).returns(#ability)
end
describe "GET index" do
it "assigns a new contact as #contact" do
#ability.can :read, Contact
Contact.stub(:new) { mock_contact }
get :index
assigns(:contact).should be(mock_contact)
end
end
describe "POST create" do
describe "with valid params" do
it "assigns a newly created contact as #contact" do
#ability.can :create, Contact
Contact.stub(:new).with({'these' => 'params'}) { mock_contact(:save => true) }
post :create, :contact => {'these' => 'params'}
assigns(:contact).should be(mock_contact)
end
it "redirects to the index of contacts" do
#ability.can :create, Contact
Contact.stub(:new) { mock_contact(:save => true) }
post :create, :contact => {}
response.should redirect_to(root_url)
end
end
describe "with invalid params" do
it "assigns a newly created but unsaved contact as #contact" do
#ability.can :create, Contact
Contact.stub(:new).with({'these' => 'params'}) { mock_contact(:save => false) }
post :create, :contact => {'these' => 'params'}
assigns(:contact).should be(mock_contact)
end
it "re-renders the 'new' template" do
#ability.can :create, Contact
Contact.stub(:new) { mock_contact(:save => false) }
post :create, :contact => {}
response.should render_template("index")
end
end
end
end
But these tests totally failed ....
I saw nothing on the web ... :(
So, if you can advise me on the way i have to follow, i would be glad to ear you :)
CanCan does not call Contact.new(params[:contact]). Instead it calls contact.attributes = params[:contact] later after it has applied some initial attributes based on the current ability permissions.
See Issue #176 for details on this and an alternative solution. I plan to get this fixed in CanCan version 1.5 if not sooner.