Rails TDD - RSpec Controller Specs

Question Click to View Answer

Create a new Rails application called controller_tdd. Add rspec_rails to the Gemfile. Install RSpec.

$ rails new controller_tdd

Gemfile
group :development, :test do
  gem "rspec-rails"
end

$ bundle exec rails generate rspec:install

Use the Rails resource generator to create files for an Article MVC with a title string attribute and a body text attribute.

$ rails g resource Article title body:text
$ rake db:migrate

Write a controller test to make sure the ArticlesController#index action sets the @articles instance variable to be an array of all the articles in the database.

spec/controllers/articles_controller_spec.rb
describe "GET index" do
  it "assigns all articles as @articles" do
    article = Article.create!(title: "valid title")
    get :index
    assigns(:articles).should eq([article])
  end
end

Write code to make the test pass.

# spec/controllers/articles_controller.rb
def index
  @articles = Article.all
end

Make the following file: app/views/articles/index.html.erb

Write a controller test to make sure the ArticlesController#show action sets the @article instance variable to the article that corresponds with the id in the parameters.

describe "GET show" do
  it "assigns the requested article as @article" do
    article = Article.create!(title: "valid title")
    get :show, {:id => article.to_param}
    expect(assigns(:article)).to eq article
  end
end

Write code to make the test pass.

# spec/controllers/articles_controller.rb
def show
  @article = Article.find(params[:id])
end

Make the following file: app/views/articles/show.html.erb

Write a controller test to make sure the ArticlesController#edit action sets the @article instance variable to the article that corresponds with the id in the parameters.

spec/controllers/articles_controller_spec.rb
describe "GET edit" do
  it "assigns the requested article as @article" do
    article = Article.create!(title: "valid title")
    get :edit, {:id => article.to_param}
    assigns(:article).should eq(article)
  end
end

Write code to make the test pass.

# app/controllers/articles_controller.rb
def edit
  @article = Article.find(params[:id])
end

Make the file app/views/articles/edit.html.erb

Write a controller tests to make sure the ArticlesController#create action creates a new article, thereby increasing the number of articles by one.

spec/controllers/articles_controller_spec.rb
describe "POST create" do
  it "creates a new article" do
    parameters = {article: {title: 'valid title'}}
    expect {post :create, parameters}.to change(Article, :count).by(1)
  end
end

Write code to make the test pass.

# app/controllers/articles_controller.rb
def create
  @article = Article.create(article_params)
  redirect_to @article
end

private

def article_params
  params.require(:article).permit(:title, :body)
end

Write a controller test to make sure the ArticlesController#create action assigns the newly created article to @article.

it "assigns a newly created article as @article" do
  post :create, {:article => {title: 'valid title'}}
  expect(assigns(:article)).to be_a(Article)
  expect(assigns(:article)).to be_persisted
end

The tests should already be passing from the code we wrote for the previous example.

Write a controller test to make sure the ArticlesController#create action redirects to the newly created article.

it "redirects to the created article" do
  post :create, {:article => {title: 'valid title'}}
  expect(response).to redirect_to(Article.last)
end

This test should already be passing from code that’s already been written.

Write a controller test to make sure the ArticlesController#create action assigns a newly created but unsaved article as @article when the parameters are invalid.

spec/controllers/articles_controller_spec.rb
it "assigns a newly created but unsaved article as @article with invalid parameters" do
  Article.any_instance.stub(:save).and_return(false)
  post :create, {:article => { "title" => "invalid value" }}
  expect(assigns(:article)).to be_a_new(Article)
end

Update the code to make the test pass.

# app/controllers/articles_controller.rb
def create
  @article = Article.new(article_params)
  if @article.save
    redirect_to @article
  else
    render :index
  end
end

Write a controller test to make sure the ArticlesController#create action renders the new page when the parameters are invalid.

# spec/controllers/articles_controller_spec.rb
it "re-renders the 'new' template with invalid parameters" do
  Article.any_instance.stub(:save).and_return(false)
  post :create, {article: {title: "invalid value" }}
  expect(response).to render_template("new")
end

Write code to make the test pass.

# app/controllers/articles_controller.rb
def new; end

def create
  @article = Article.new(article_params)
  if @article.save
    redirect_to @article
  else
    render :new
  end
end

Make the following file: app/views/articles/new.html.erb

Write a controller test to make sure that the ArticlesController#update action updates an article.

# spec/controllers/articles_controller_spec.rb
describe "PUT update" do
  it "updates the requested article" do
    article = Article.create!(title: "something")
    Article.any_instance.should_receive(:update).with({ "title" => "new title" })
    put :update, {:id => article.to_param, :article => { "title" => "new title" }}
  end
end

Write code to make the test pass.

# app/controllers/articles_controller.rb
def update
  @article = Article.find(params[:id])
  @article.update(article_params)
  redirect_to articles_path
end

Add a test to make sure the ArticlesController#update action assigns the @article instance variable to the requested article.

# spec/controllers/articles_controller_spec.rb
it "assigns the requested article as @article" do
  article = Article.create!(title: 'something')
  put :update, {:id => article.to_param, :article => {"title" => "new title"}}
  assigns(:article).should eq(article)
end

This test should be passing from code that was already added.

Add a test to make sure that the ArticlesController#update action redirects to the article show page after the article is updated.

# spec/controllers/artices_controller_spec.rb
it "redirects to the article" do
  article = Article.create!(title: 'something')
  put :update, {:id => article.to_param, :article => {"title" => "new title"}}
  expect(response).to redirect_to(article)
end

Update the code to make the test pass.

# app/controllers/articles_controller.rb
def update
  @article = Article.find(params[:id])
  @article.update(article_params)
  redirect_to @article
end

Write a test to make sure the ArticlesController#destroy action deletes an article.

# spec/controllers/articles_controller_spec.rb
describe "DELETE destroy" do
  it "destroys the requested article" do
    article = Article.create!(title: 'something')
    expect do
      delete :destroy, {:id => article.to_param}
    end.to change(Article, :count).by(-1)
  end
end

Update the code to make the test pass.

# app/controllers/articles_controller.rb
def destroy
  @article = Article.find(params[:id])
  @article.destroy
  redirect_to articles_path
end

Write a test to make sure the ArticlesController#destroy action redirects to the index page.

# spec/controllers/articles_controller_spec.rb
it "redirects to the posts list" do
  article = Article.create!(title: "something")
  delete :destroy, {:id => article.to_param}
  expect(response).to redirect_to(articles_url)
end