In solving exercise 5, I ran into a similar issue as this question. I refactored based on the answer given to that question but am still receiving the failure:
1) UsersController DELETE 'destroy' as an admin user should not self-destruct
Failure/Error: lambda do
count should have been changed by 0, but was changed by -1
# ./spec/controllers/users_controller_spec.rb:354:in `block (4 levels) in <top (required)>'
My spec:
it "should destroy the user" do
lambda do
delete :destroy, :id => #user
end.should change(User, :count).by(-1)
end
it "should redirect to the users page" do
delete :destroy, :id => #user
response.should redirect_to(users_path)
end
it "should not self-destruct" do
lambda do
delete :destroy, :id => #user.id
end.should change(User, :count).by(0)
end
and my controller:
def destroy
#user = User.find(params[:id])
if current_user == #user
flash[:notice] = "You cannot destroy yourself"
else
#user.destroy
flash[:success] = "User destroyed"
end
redirect_to users_path
end
I've checked the behavior in the browser and it works as expected. As always, any help is appreciated. Thanks!
The updated, working code:
describe "as an admin user" do
before(:each) do
#admin = Factory(:user, :email => "admin#example.com", :admin => "true")
test_sign_in(#admin)
end
it "should have links to destroy a user" do
get :index
response.should have_selector("a", :content => "delete" )
end
it "should destroy the user" do
lambda do
delete :destroy, :id => #user
end.should change{ User.count }.by(-1)
end
it "should redirect to the users page" do
delete :destroy, :id => #user
response.should redirect_to(users_path)
end
it "should not be allowed to delete itself" do
lambda do
delete :destroy, :id => #admin
end.should_not change{ User.count }
end
end
I just realised that I read the wrong one of the three tests you posted (it would be clearer if you only posted the failing test :)
However I'm confused, your "it should not self destruct" test is exactly the same as "it should destroy the user":
it "should destroy the user" do
lambda do
delete :destroy, :id => #user
end.should change(User, :count).by(-1)
end
it "should not self-destruct" do
lambda do
delete :destroy, :id => #user.id
end.should change(User, :count).by(0)
end
You're running the same test both times but expecting different outcomes. I would take a look again at the "it should not self destruct" and see what you mean by that (I'm not sure).
I would guess that what needs to change is whatever is in the `change( xxxx ).by(0). What is it that shouldn't be changing through the test?
This bit me for a long time too. Try changing your syntax from:
should change(User, :count).by(-1)
to:
should change{ User.count }.by(-1)
Or try doing:
This is getting a little away from the book but you could try changing your syntax. The RSpec recommendation for this type of expectation is:
it "should destroy the user" do
expect{
delete :destroy, :id => #user
}.to change{ User.count }.by(-1)
end
Which I actually think is much cleaner and more readable anyway. Could you provide a link to the example as it is in the book?
Related
I'm trying to test my app so that when a user votes for a certain kind of food, both the count of user.votes goes up as well as food.votes. As of now I can't that to happen, and I'm not sure if I've written my tests wrong or have set up a factory that will not make it possible for the tests to pass. It works when I test manually in the console, so I know that it does function, but the tests do not agree.
FactoryGirl.define do
factory :vote do |f|
f.user_id user
f.food_id food
end
end
def create
#vote = Vote.new(vote_params)
if #vote.save
flash[:notice] = "Thanks for voting!"
else
flash[:notice] = "Something went wrong"
end
end
before(:each) do
#user = create(:user)
sign_in(#user)
#food = create(:food)
end
describe "POST #create" do
context "with valid attributes" do
it "creates a new vote" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(Vote, :count).by(1)
end
it "Updates #user vote count" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
end
it "Updates #food vote count" do
vote_params = FactoryGirl.attributes_for(:vote)
expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
end
end
end
Failures:
1) VotesController POST #create with valid attributes Updates #user vote count
Failure/Error: expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/votes_controller_spec.rb:27:in `block (4 levels) in <top (required)>'
2) VotesController POST #create with valid attributes Updates #food vote count
Failure/Error: expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/votes_controller_spec.rb:32:in `block (4 levels) in <top (required)>'
Finished in 2.99 seconds (files took 4.66 seconds to load)
36 examples, 2 failures
Failed examples:
rspec ./spec/controllers/votes_controller_spec.rb:25 # VotesController POST #create with valid attributes Updates #user vote count
rspec ./spec/controllers/votes_controller_spec.rb:30 # VotesController POST #create with valid attributes Updates #food vote count
Ok, confident enough to take a guess. The issue, I think, is that vote_params is coming from a factory (probably with hard-coded values for user_id/food_id) - but not the real #user and #food that you are testing against. You need to put these values into your vote_params can I suggest something like the following:
context "with valid attributes" do
# Set vote-params from factory, then merge in the #user/#food ids
let(:vote_params) { FactoryGirl.attributes_for(:vote).merge(:user_id => #user.id, :vote_id => #vote.id) }
it "creates a new vote" do
expect { post :create, params: { vote: vote_params } }.to change(Vote, :count).by(1)
end
it "Updates #user vote count" do
expect { post :create, params: { vote: vote_params } }.to change(#user.votes, :count).by(1)
end
it "Updates #food vote count" do
expect { post :create, params: { vote: vote_params } }.to change(#food.votes, :count).by(1)
end
end
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 this controller spec file.
describe BriefNotesController do
render_views
before(:all) do
#customer=Factory(:customer)
#project=Factory(:project_started, :owner => #customer)
end
context 'get :new' do
it 'should redirect to login page for not signed in users' do
get :new, :project_id => #project.id
response.should new_user_session_path
end
end
context 'get :create' do
it 'should redirect to login page for not signed in users' do
post :create, {:project_id => #project.to_param, :brief_note => Factory.attributes_for(:brief_note, :project_id => #project.id)}
response.should redirect_to new_user_session_path
end
end
In these tests I repeat the same authentication test for each action and thus I want to recode it with dry way.I tried to recode this test with shared_examples_for method but I didn't handle with it.
Something like this can work
shared_examples_for 'redirects to login page if not signed in' do
context 'get :new' do
it 'should redirect to login page for not signed in users' do
get :new, :project_id => resource.id
response.should redirect_to new_user_session_path
end
end
context 'get :create' do
it 'should redirect to login page for not signed in users' do
post :create, resource_params
response.should redirect_to new_user_session_path
end
end
end
You would use it like this:
describe BriefNoteController do
let(:customer) { Factory(:customer) }
let(:project) { Factory(:project_started, :owner => customer) }
let(:resource) { project }
let(:resource_params) do
{
:project_id => project.to_param,
:brief_note => Factory.attributes_for(:brief_note, :project_id => project.id)
}
end
it_behaves_like 'redirects to login page if not signed in'
end
Here is my current users_controller_spec.rb file
require 'spec_helper'
describe UsersController do
render_views
.
.
.
describe "success" do
before(:each) do
#attr = { :name => "New User", :email => "user#example.com",
:password => "foobar", :password_confirmation => "foobar" }
end
it "should create a user" do
lambda do
post :create, :user => #attr
end.should change(User, :count).by(1)
end
it "should redirect to the user show page" do
post :create, :user => #attr
response.should redirect_to(user_path(assigns(:user)))
end
end
end
end
When I run this I get the following:
Failures:
1) UsersController POST 'create' success should redirect to the user show page
Failure/Error: response.should redirect_to(user_path(user))
ActionController::RoutingError:
No route matches {:action=>"show", :controller=>"users"}
# ./spec/controllers/users_controller_spec.rb:95:in `block (4 levels) in <top (required)>'
Which leads me to believe that :user isn't an actual object. How can I test this and how can I change :user into an object that user_path understands.
Thanks in advance for any help.
UPDATED:
def create
#title = "Sign up"
#user = User.new(params[:user])
if #user.save
redirect_to #user, :notice => "Signed Up!"
else
#title = "Sign up"
render "new"
end
end
When I run the following:
it "should redirect to the user show page" do
post :create, :user => #attr
user = assigns(:user)
user.should_not be_blank
puts "user errors are: #{user.errors.full_messages.inspect}" unless user.is_valid?
user.should be_valid
response.should redirect_to(user_path(user))
end
I get:
1) UsersController POST 'create' success should redirect to the user show page
Failure/Error: user.should_not be_blank
expected blank? to return false, got true
# ./spec/controllers/users_controller_spec.rb:94:in `block (4 levels) in <top (required)>'
Try using assigns with brackets instead of parenthesis.
assigns[:user]
(RSpec docs)
EDIT
One pattern I always use when testing controller actions is to ensure that a variable required by the associated view(s) is assigned. For example,
it "should assign an #user variable" do
post :create, :user => #attr
assigns[:user].should_not be_nil
assigns[:user].should be_kind_of(User)
end
You've got a typo
response.should redirect_to(user_path(:user))
should be
response.should redirect_to(user_path(user))
Edit:
Try checking that the user is valid with:
it "should redirect to the user show page" do
post :create, :user => #attr
user = assigns(:user)
user.should_not be_blank
puts "user errors are: #{user.errors.full_messages.inspect}" unless user.is_valid?
user.should be_valid
response.should redirect_to(user_path(user))
end
I know it works on the previous test case... but still worth checking extensively at least once here. You can remove all that guff when you're certain.
In your test:
it "should redirect to the user show page" do
#user = Factory(:user)
post :create, :user => #attr
user = assigns(:user)
response.should redirect_to(user_path(#user))
end
Your #attr hash is different from #user. You are creating user with attributes of #attr, and asserting that it should redirect to #user show (which is different from one created). Change to:
it "should redirect to the user show page" do
post :create, :user => #attr
user = assigns(:user)
response.should redirect_to(user_path(user))
end
I'm having trouble understanding why I can't seem to stub this controller method :load_user, since all of my tests fail if I change the actual implementation of :load_user to not return and instance of #user.
Can anybody see why my stub (controller.stub!(:load_user).and_return(#user)) seems to fail to actually get called when RSpec makes a request to the controller?
require 'spec_helper'
describe TasksController do
before(:each) do
#user = Factory(:user)
sign_in #user
#task = Factory(:task)
User.stub_chain(:where, :first).and_return(#user)
controller.stub!(:load_user).and_return(#user)
end
#GET Index
describe "GET Index" do
before(:each) do
#tasks = 7.times{Factory(:task, :user => #user)}
#user.stub!(:tasks).and_return(#tasks)
end
it "should should find all of the tasks owned by a user" do
#user.should_receive(:tasks).and_return(#tasks)
get :index, :user_id => #user.id
end
it "should assign all of the user's tasks to the view" do
get :index, :user_id => #user.id
assigns[:tasks].should be(#tasks)
end
end
#GET New
describe "GET New" do
before(:each) do
#user.stub_chain(:tasks, :new).and_return(#task)
end
it "should return a new Task" do
#user.tasks.should_receive(:new).and_return(#task)
get :new, :user_id => #user.id
end
end
#POST Create
describe "POST Create" do
before(:each) do
#user.stub_chain(:tasks, :new).and_return(#task)
end
it "should create a new task" do
#user.tasks.should_receive(:new).and_return(#task)
post :create, :user_id => #user.id, :task => #task.to_s
end
it "saves the task" do
#task.should_receive(:save)
post :create, :user_id => #user.id, :task => #task
end
context "when the task is saved successfully" do
before(:each) do
#task.stub!(:save).and_return(true)
end
it "should set the flash[:notice] message to 'Task Added Successfully'"do
post :create, :user_id => #user.id, :task => #task
flash[:notice].should == "Task Added Successfully!"
end
it "should redirect to the user's task page" do
post :create, :user_id => #user.id, :task => #task
response.should redirect_to(user_tasks_path(#user.id))
end
end
context "when the task isn't saved successfully" do
before(:each) do
#task.stub(:save).and_return(false)
end
it "should return to the 'Create New Task' page do" do
post :create, :user_id => #user.id, :task => #task
response.should render_template('new')
end
end
end
it "should attempt to authenticate and load the user who owns the tasks" do
context "when the tasks belong to the currently logged in user" do
it "should set the user instance variable to the currently logged in user" do
pending
end
end
context "when the tasks belong to another user" do
it "should set the flash[:notice] to 'Sorry but you can't view other people's tasks.'" do
pending
end
it "should redirect to the home page" do
pending
end
end
end
end
class TasksController < ApplicationController
before_filter :load_user
def index
#tasks = #user.tasks
end
def new
#task = #user.tasks.new
end
def create
#task = #user.tasks.new
if #task.save
flash[:notice] = "Task Added Successfully!"
redirect_to user_tasks_path(#user.id)
else
render :action => 'new'
end
end
private
def load_user
if current_user.id == params[:user_id].to_i
#user = User.where(:id => params[:user_id]).first
else
flash[:notice] = "Sorry but you can't view other people's tasks."
redirect_to root_path
end
end
end
Can anybody see why my stub doesn't work? Like I said, my tests only pass if I make sure that load_user works, if not, all my tests fail which makes my think that RSpec isn't using the stub I created.
Stubbing out load_user breaks your tests because stubbing the method neuters it. When the controller calls load_user, it is no longer running your original code. It's now just returning whatever you specify in and_return(...) (which is getting returned to the ActionController callback stack, which ignores anything other than false).
Your controller code isn't using the return value of that method; it's using the variable instantiated within it. Since the original code for the load_user method isn't being run, the #user instance variable is never instantiated. (The #user variable in your tests is only visible to your tests.)
But with all the other stubs you have, I don't see any reason why you should need to stub out load_user at all. As long as you're stubbing current_user to return #user (which I assume is being done in the sign_in method), then there shouldn't be any need.
you can also try to verify that the stub works by doing an assertion like
controller.current_user.should == #user