Hi, guys,
After reading the rspec book (dec 2010 edition), I went on to write
controller specs for an application I'm porting over from rails 2.3.x
to rails 3.
1) I ran 'rake routes' and got the following:
parts GET /parts(.:format)
{:action=>"index", :controller=>"parts"}
POST /parts(.:format)
{:action=>"create", :controller=>"parts"}
new_part GET /parts/new(.:format)
{:action=>"new", :controller=>"parts"}
edit_part GET /parts/:id/edit(.:format)
{:action=>"edit", :controller=>"parts"}
part GET /parts/:id(.:format)
{:action=>"show", :controller=>"parts"}
PUT /parts/:id(.:format)
{:action=>"update", :controller=>"parts"}
DELETE /parts/:id(.:format)
{:action=>"destroy", :controller=>"parts"}
2) I ran 'rake spec:controllers' and got the error below.
Pending:
PartsController the new part object's updates are saved successfully
saves updates to a new part
# Not Yet Implemented
# ./spec/controllers/parts_controller_spec.rb:59
Failures:
1) PartsController saves updates to an existing part object
successfully
Failure/Error: put :update#, :part => { 'title' => 'Grimspeed' }
ActionController::RoutingError:
No route matches {:controller=>"parts", :action=>"update"}
# ./spec/controllers/parts_controller_spec.rb:55
3) Here's what the spec reads:
it 'saves updates to an existing part object successfully' do
Part.should_receive(:update).
with( 'title' => 'Brake pads' ).
and_return(part)
put :update#, :part => { 'title' => 'Brake pads' }
end
What I do not understand is that
the :controller=>"parts", :action=>"update" route actually exists
(when I ran "rake routes") but the tests does not acknowledge the
existence of the 'update' method in the controller with PUT request.
on 2011-08-22 08:16
on 2011-08-22 09:44
On Mon, Aug 22, 2011 at 12:14 AM, ct9a <anexiole@gmail.com> wrote: > {:action=>"index", :controller=>"parts"} > POST /parts(.:format) > {:action=>"create", :controller=>"parts"} > new_part GET /parts/new(.:format) > {:action=>"new", :controller=>"parts"} > edit_part GET /parts/:id/edit(.:format) > {:action=>"edit", :controller=>"parts"} > part GET /parts/:id(.:format) > {:action=>"show", :controller=>"parts"} > PUT /parts/:id(.:format) > The :id means you need to pass an "id" to the route helper method. > # ./spec/controllers/parts_controller_spec.rb:59 > 3) Here's what the spec reads: > > it 'saves updates to an existing part object successfully' do > Part.should_receive(:update). > with( 'title' => 'Brake pads' ). > and_return(part) > put :update#, :part => { 'title' => 'Brake pads' } > end > So you need to "find" the part first: part = double('part') Part.should_receive(:find).with(1).and_return(part) Part.should_receive(:update).with('title' => 'Brake pads').and_return(part) put :update, :id => 1, :part => {'title' => 'Brake pads'} > > > > > What I do not understand is that > the :controller=>"parts", :action=>"update" route actually exists > (when I ran "rake routes") but the tests does not acknowledge the > existence of the 'update' method in the controller with PUT request. > You need to pass in the :id for Rails to recognize the route.
on 2011-08-23 05:52
Thanks, Justin.
I have the part object mocked up before each spec runs.
------- Extract begins -----------------
let(:part){
mock_model('Part').as_null_object
}
before do
Part.stub(:new).and_return(part)
end
------- Extract ends -----------------
With that in mind, I just made changes to my spec as below:
------- Extract begins -----------------
it 'saves updates to an existing part object successfully' do
Part.should_receive(:find).with(1).and_return(part)
Part.should_receive(:update).with( 'title' =>
'Grimspeed' ).and_return(part)
put :update, :id => 1, :part => { 'title' => 'Grimspeed' }
end
------- Extract ends -----------------
When I run the 'rake spec:controllers', I get the following error:
------- Error extract begins -----------------
1) PartsController saves updates to an existing part object
successfully
Failure/Error: Part.should_receive(:update).with( 'title' =>
'Brake pads' ).and_return(part)
(<Part(id: integer, title: string, description: text,
created_by: string, updated_by: string, created_at: datetime,
updated_at: datetime) (class)>).update({"title"=>"Brake pads"})
expected: 1 time
received: 0 times
# ./spec/controllers/parts_controller_spec.rb:53
------- Error extract ends -----------------
I then put in Justin's change with the use of double() as follows:
------- Source code extract begins -----------------
it 'saves updates to an existing part object successfully' do
part = double(part)
Part.should_receive(:find).with(1).and_return(part)
Part.should_receive(:update).with( 'title' => 'Brake
pads' ).and_return(part)
put :update, :id => 1, :part => { 'title' => 'Brake pads' }
end
------- Source code extract ends -----------------
and I got the following error:
------- Error extract begins -----------------
1) PartsController saves updates to an existing part object
successfully
Failure/Error: put :update, :id => 1, :part => { 'title' =>
'Brake pads' }
Double received unexpected message :update_attributes with
({"title"=>"Brake pads"})
# ./app/controllers/parts_controller.rb:63:in `update'
# ./app/controllers/parts_controller.rb:62:in `update'
# ./spec/controllers/parts_controller_spec.rb:55
------- Error extract ends -----------------
I have also looked at a few other posts in the group but can't figure
out what's causing
update_attributes not to be recognised :(
-
http://groups.google.com/group/rspec/browse_thread...
-
http://groups.google.com/group/rspec/browse_thread...
My update message in the parts controller reads as below:
------- Source code extract starts -----------------
def update
@part = Part.find(params[:id])
respond_to do |format|
if @part.update_attributes(params[:part])
format.html { redirect_to(@part, :notice => 'Part was
successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @part.errors, :status
=> :unprocessable_entity }
end
end
end
------- Source code extract ends -----------------
I have used this bit of code without problems in the past.
update_attributes, like save, is part of the ActiveRecord api (http://
ar.rubyonrails.org/).
Well, given that controllers are not to know of the implementation of
messages,
I have then stubbed the message to the update_attributes method in the
parts controller.
------- Source code extract starts -----------------
context 'saves updates to an existing part object successfully' do
before do
part =
double('part').stub(:update_attributes).and_return(true)
end
it 'does its job in saving the update' do
Part.should_receive(:find).with(1).and_return(part)
Part.should_receive(:update).with('title' => 'Brake
pads').and_return(part)
put :update, :id => 1, :part => {'title' => 'Brake pads'}
end
end
------- Source code extract ends -----------------
Alas, I got the error below :(
------- Error extract starts -----------------
1) PartsController saves updates to an existing part object
successfully does its job in saving the update
Failure/Error: Part.should_receive(:update).with('title' =>
'Brake pads').and_return(part)
(<Part(id: integer, title: string, description: text,
created_by: string, updated_by: string, created_at: datetime,
updated_at: datetime) (class)>).update({"title"=>"Brake pads"})
expected: 1 time
received: 0 times
# ./spec/controllers/parts_controller_spec.rb:61
------- Error extract ends -----------------
on 2011-08-23 05:58
On Mon, Aug 22, 2011 at 9:09 PM, ct9a <anexiole@gmail.com> wrote: > } > ------- Extract begins ----------------- > > it 'saves updates to an existing part object successfully' do > Part.should_receive(:find).with(1).and_return(part) > Part.should_receive(:update).with( 'title' => > 'Grimspeed' ).and_return(part) > Should be: Part.should_receive(:update_attributes).with('title' => 'Grimspeed').and_return(part) No where do you call `update` on Part.
on 2011-08-23 06:16
On Aug 23, 1:55pm, Justin Ko <jko...@gmail.com> wrote: > > } > > No where do you call `update` on Part. Yes, I have done that and the spec looks like below. ------------------ Spec extract begins ------------------------------------------ context 'saves updates to an existing part object successfully' do it 'does its job in saving the update' do Part.should_receive(:find).with(1).and_return(part) Part.should_receive(:update_attributes).with('title' => 'Brake pads').and_return(part) put :update, :id => 1, :part => {'title' => 'Brake pads'} end end ------------------ Spec extract ends ------------------------------------------ Alas, it's still giving out an error indicating the update_attributes message is never called. I've already got a put request to the 'update' method with an id and the part hash parameter and yet it looks like 'update' is not called (which in will call update_attributes in it). ----------------- Error extract starts ------------------------------------------------- 1) PartsController saves updates to an existing part object successfully does its job in saving the update Failure/Error: Part.should_receive(:update_attributes).with('title' => 'Brake pads').and_return(part) (<Part(id: integer, title: string, description: text, created_by: string, updated_by: string, created_at: datetime, updated_at: datetime) (class)>).update_attributes({"title"=>"Brake pads"}) expected: 1 time received: 0 times # ./spec/controllers/parts_controller_spec.rb:54 Finished in 0.25483 seconds 8 examples, 1 failure, 1 pending ----------------- Error extract end
on 2011-08-23 14:02
Sent from my iPhone On Aug 22, 2011, at 10:14 PM, ct9a <anexiole@gmail.com> wrote: >> >>> With that in mind, I just made changes to my spec as below: >> Part.should_receive(:update_attributes).with('title' => > Part.should_receive(:find).with(1).and_return(part) > I've already got a put request to the 'update' method with an id and > Part.should_receive(:update_attributes).with('title' => 'Brake > 8 examples, 1 failure, 1 pending > > ----------------- Error extract end > ------------------------------------------------- > > Hmm.... What is missing? Do you have a before filter somewhere that is preventing the :update_attributes message from being received?
on 2011-08-23 14:46
> > > Do you have a before filter somewhere that is preventing the > :update_attributes message from being received? > > I have checked my application's controllers (app/controllers/application_controller.rb and app/controllers/parts_controller.rb) and controller spec (spec/controllers/parts_controller_spec.rb) and found no before filter that is preventing the :update_attributes message from being received by Parts. Below is what my spec/controllers/parts_controller_spec.rb looks like: ------- Source code, spec/controllers/parts_controller_spec.rb starts ----------------------- require 'spec_helper' describe PartsController do let(:part){ mock_model('Part').as_null_object } before do Part.stub(:new).and_return(part) end it "creates a new part" do Part.should_receive(:new). with('title' => 'HKS boost controller'). and_return(part) post :create, :part => { 'title' => 'HKS boost controller' } end context "saves the new part object successfully" do it "sets the flash with a success message" do post :create flash[:notice].should eq('Part was successfully created.') end it "redirects the user back to the Parts index page" do post :create response.should redirect_to( :action => 'index' ) end end context "the new part object fails to save" do before do # sabotage and make the save fail part.stub(:save).and_return(false) # call the create method which will have save in its process post :create end it "assigns @part with the data from db (or a blank one if it's the first time" do assigns[:part].should eq(part) end it "renders the 'new' template again" do # test that the template, 'new' is rendered. Of course, for this # work, the part's attribute variable values must be passed to the # view for rails to successfully render the template response.should render_template('new') end end context 'saves updates to an existing part object successfully' do it 'does its job in saving the update' do Part.should_receive(:find).with(1).and_return(part) Part.should_receive(:update_attributes).with('title' => 'Brake pads').and_return(part) put :update, :id => 1, :part => {'title' => 'Brake pads'} flash[:notice].should eq('Part was successfully updated.') end end end ------- Source code, spec/controllers/parts_controller_spec.rb ends
on 2011-08-23 14:51
I found out why it was not working.
The line, 'Part.should_receive(:update_attributes).with('title' =>
'Brake
pads').and_return(part)' should not be there because the controller
specs
should not care about implementation (ie. how things are processed,
rather
just what is done). I realised this when I looked at my specs for
creation
of new objects.
I commented the line and the spec now passes as expected.
------------ Spec extract starts -------------------------
context 'saves updates to an existing part object successfully' do
it 'does its job in saving the update' do
Part.should_receive(:find).with(1).and_return(part)
# Part.should_receive(:update_attributes).with('title' =>
'Brake
pads').and_return(part)
put :update, :id => 1, :part => {'title' => 'Brake pads'}
flash[:notice].should eq('Part was successfully updated.')
end
end
------------ Spec extract ends -------------------------
Thank you for your help, Justin!
Gordon Yeong :)
on 2011-08-23 14:58
On Aug 23, 2011, at 7:22 AM, Gordon Yeong wrote: > it 'does its job in saving the update' do > Part.should_receive(:find).with(1).and_return(part) > # Part.should_receive(:update_attributes).with('title' => 'Brake pads').and_return(part) ^^ Change the class name Part to the variable part ^^: part.should_receive(:update_attributes).with('title' => 'Brake pads').and_return(part) It's the part object, not the Part class that will receive update_attributes. HTH, David > _______________________________________________ > rspec-users mailing list > rspec-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users Cheers, David
on 2011-08-23 14:59
I think you were closer when you had:
------- Source code extract starts -----------------
context 'saves updates to an existing part object successfully' do
before do
part =
double('part').stub(:update_
attributes).and_return(true)
end
it 'does its job in saving the update' do
Part.should_receive(:find).with(1).and_return(part)
Part.should_receive(:update).with('title' => 'Brake
pads').and_return(part)
put :update, :id => 1, :part => {'title' => 'Brake pads'}
end
end
------- Source code extract ends -----------------
However, you were setting part equal to the return value from
"stub(:update_attributes).and_return(true)" which returns a Proc, not a
double.
Instead, try this:
let(:part) { double('part') }
it 'does its job in saving the update' do
part.stub(:update_attributes).and_return(true) # <--- moved
this
from the before method
Part.should_receive(:find).with(1).and_return(part)
Part.should_receive(:update).with('title' => 'Brake
pads').and_return(part)
put :update, :id => 1, :part => {'title' => 'Brake pads'}
end
end
on 2011-08-23 15:00
On Tue, Aug 23, 2011 at 6:11 AM, Gordon Yeong <anexiole@gmail.com> wrote: > Below is what my spec/controllers/parts_controller_spec.rb looks like: > > > end > first time" do > end > end > > context 'saves updates to an existing part object successfully' do > it 'does its job in saving the update' do > Part.should_receive(:find).with(1).and_return(part) > Part.should_receive(:update_attributes).with('title' => 'Brake > pads').and_return(part) > Oh duh. You're calling `should_receive(:update_attributes)` on `Part` when it should be called on `part`: part.should_receive(:update_attributes).with('title' => 'Brake pads').and_return(true)
on 2011-08-23 15:41
On Tue, Aug 23, 2011 at 6:22 AM, Gordon Yeong <anexiole@gmail.com> wrote: > I found out why it was not working. > > The line, 'Part.should_receive(:update_attributes).with('title' => 'Brake > pads').and_return(part)' should not be there because the controller specs > should not care about implementation (ie. how things are processed, rather > just what is done). > Depends on what you want to do. Now, your controller spec is coupled to your model, so if "update_attributes" fails, your controller spec will fail too.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.