I'm trying to solidify my understanding of rails and the BDD workflow, so I wanted to start small by creating one of those mini-blogs, but with rspec. Right now I have an ArticlesController and Article model, and associated rspec files. Article is very simple, has just title:string and content:text, and the ArticlesController is RESTful - although I hand wrote the MCV for Article, it's basically the same as if I used a scaffold to create it.
However I don't really know what I'm doing when it comes to writing a test in rspec for the PUT update. I'm using Factory Girl to create the article object, and so far my code looks like:
#factories.rb
FactoryGirl.define do
factory :article do
title "a title"
content "hello world"
end
#articles_controller_spec.rb
before(:each) do
@article = Factory(:article)
end
describe "PUT 'update/:id'" do
it "allows an article to be updated" do
@attr = { :title => "new title", :content => "new content" }
put :update, :id => @article.id, :article => @attr
response.should be_successful
end
end
However I keep getting:
Failures:
1) ArticlesController PUT 'update/:id' allows an article to be updated
Failure/Error: response.should be_successful
expected successful? to return true, got false
What am I doing wrong? And am I using the right tools? When I run my test server, New, Edit, Destroy all work as I would expect them to, so I'm guessing this is a problem with my understanding of RSpec. Let me know if I'm wrong - thanks!
You forgot to .reload
your @article
, and on update
action your response most likely perform redirect, so
RSpec 2:
describe "PUT update/:id" do
let(:attr) do
{ :title => 'new title', :content => 'new content' }
end
before(:each) do
put :update, :id => @article.id, :article => attr
@article.reload
end
it { response.should redirect_to(@article) }
it { @article.title.should eql attr[:title] }
it { @article.content.should eql attr[:content] }
end
Rspec 3:
describe "PUT update/:id" do
let(:attr) do
{ :title => 'new title', :content => 'new content' }
end
before(:each) do
put :update, :id => @article.id, :article => attr
@article.reload
end
it { expect(response).to redirect_to(@article) }
it { expect(@article.title).to eql attr[:title] }
it { expect(@article.content).to eql attr[:content] }
end
When you are doing a PUT :update
remember that you are editing an existing model, which you need to call in the put
. Just pass your @article
and update the attributes as follows.
describe "PUT 'update/:id'" do
it "allows an article to be updated" do
put :update, :id => @article.id, :article => @article.attributes = { :title => "new title", :content => "new content" }
response.should be_successful
end
end
FactoryGirl.define :article do
title "a title"
content "hello world"
end
before(:each) do
@article = Factory(:article)
end
it "should re-render edit template on failed update" do
@attr = { :title => "", :content => "new content" }
put :update, :id => @article.id, :article => @attr
flash[:notice].should be_nil
response.should render_template('edit')
end
it "should redirect to index with a notice on successful update" do
@attr = { :title => "new title", :content => "new content" }
put :update, :id => @article.id, :article => @attr
assigns[:article].should_not be_new_record
flash[:notice].should_not be_nil
response.should redirect_to(:action => 'index')
end
The way I like to test the update method is to just ensure that the updated_at time is greater than it was before. When you do this you can change the contents of the entire instance variable and still check if everything was updated. For instance:
describe "PUT 'update/:id'" do
it "allows an article to be updated" do
prev_updated_at = @article.updated_at
@attr = { :title => "new title", :content => "new content" }
put :update, :id => @article.id, :article => @attr
@article.reload
@article.updated_at.should != prev_updated_at
end
end