I'm trying to learn RSpec and writing test for CRUD actions. Here is my controller:
class ArticlesController < ApplicationController
respond_to :html, :json
before_filter :authenticate_user!
# GET /articles
# GET /articles.json
def index
@articles = current_user.articles.all
respond_with(@articles)
end
# GET /articles/1
# GET /articles/1.json
def show
@article = current_user.articles.find(params[:id])
respond_with @article
end
# GET /articles/new
# GET /articles/new.json
def new
@article = current_user.articles.build
respond_with @article
end
# GET /articles/1/edit
def edit
@article = get_article(params[:id])
end
# POST /articles
# POST /articles.json
def create
@article = current_user.articles.build(params[:article])
flash[:notice] = "Article was successfully created!" if @article.save
respond_with(@article, location: articles_path)
end
# PUT /articles/1
# PUT /articles/1.json
def update
@article = get_article(params[:id])
if @article.update_attributes(params[:article])
flash[:notice] = "Article was successfully updated."
end
respond_with @article
end
# DELETE /articles/1
# DELETE /articles/1.json
def destroy
@article = get_article(params[:id])
@article.destroy
respond_with @article
end
private
def get_article(article_id)
current_user.articles.find(article_id)
end
end
And my articles rspec:
describe ArticlesController do
def valid_attributes
{
:title => "Introducting Node.js",
:content => "Node.js is an event-driven...."
}
end
let(:article) do
build(:article, valid_attributes)
end
describe "PUT 'update'" do
before(:each) do
controller.stub_chain(:current_user, :articles, :build) { article }
end
context "success" do
before(:each) do
article.should_receive(:update_attributes).and_return(true)
put :update, id: article.id
end
it "sets notice" do
flash[:notice].should eq("Article was successfully updated!")
end
end
end
describe "POST 'create'" do
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 notice" do
flash[:notice].should eq("Article was successfully created!")
end
it "should redirect to article path" do
response.should redirect_to(articles_path)
end
end
context "failure" do
before(:each) do
article.should_receive(:save).and_return(false).as_null_object
post :create
end
it "assigns @article" do
assigns(:article).should == article
end
end
end
end
My question is when I run rspec on PUT UPDATE test is failed. But POST test is passed. I don't have any idea what is going on. I'm using Rails 3.1.1 with omniauth. I'm not using Devise. Here is the test result. Why? Please help me guys?
Failures:
1) ArticlesController PUT 'update' success sets notice
Failure/Error: put :update, id: article.id
NoMethodError:
undefined method `find' for #<Object:0xa3cfd20>
# ./app/controllers/articles_controller.rb:61:in `get_article'
# ./app/controllers/articles_controller.rb:44:in `update'
# ./spec/controllers/articles_controller_spec.rb:46:in `block (4 levels) in <top (required)>'
Finished in 24.09 seconds
5 examples, 1 failure
Here's the thing.
When you're stubbing, you're just saying "if this method chain is called, return this." There are two issues with that. 1) the code doesn't ever call build
, and 2) there's no actual associations.
I believe you'd need to stub current_user.articles
to return an article collection. The problem is that AR associations aren't actual arrays, they're proxies.
See this SO post and this SO post for more details. A regular array won't treat the find
method like the AR method it really is, and you're not returning a single article.
Since you have the article ID, you could just return that particular article, but your goal is to return that article from within the user's articles to avoid updating someone else's (I assume).
This SO post may also help, and this.
In other words, you may want a real user there, with real associated objects, so things like find
will work w/o hackery.
(I fully recognize this isn't a real answer; I've never done this via stubbing, I've used factories/etc.)