I want to check whether the create action passes.
describe "#create" do
let!(:user){ create(:user) }
let!(:post){ create_list(:post, 3, user: user) }
context "authenticated user" do
it "adds a new post" do
post_params = FactoryBot.attributes_for(:post)
sign_in user
expect{ post :create, params: {post: post_params} }.to change(user.posts, :count).by(1)
end
end
end
but my error is
Failure/Error: expect{ post :create, params: {post: post_params} }.to change(user.posts, :count).by(1)
ArgumentError:
wrong number of arguments (given 2, expected 0)
posts_controller:
def create
#post = Post.new(post_params)
if params[:images]
if #post.save
params[:images].each do |img|
#post.photos.create(image: img)
end
else
end
redirect_to posts_path
flash[:notice] = "success"
else
redirect_to posts_path
flash[:alert] = "failure"
end
end
It's most probably because the variable you are using, let!(:post), is clashing with post :create, params: {post: post_params} (the post method).
Solution is to change let!(:post) to let!(:posts) since it is a list anyway.
Testing RESTful actions of multiple Rails controllers with RSpec can generate a lot of code repetition. The following code is my first attempt at using shared examples to DRY things up.
Here is what I don't like about the code, could not find a better way and would like your help to improve:
The shared examples require that specific variables are set within let blocks within the controller spec (high coupling). I have tried to use the model name to infer the factory name and create the test data within the share examples. It works well to create the record and records variables. However, some models require the presence of associations and FactoryGirl.attributes_for does not create associated records, so validation fails. So, valid_attributes are created differently for different models. The only (likely bad) way I could think of creating valid_attributes within shared examples is to pass a string containing the code used to create the attributes and evaluate it (eval) within the shared examples
The tests that assert redirection use eval to call Rails' route/path helpers. Different controllers in this app have different redirect behaviors. After creating or updating a record, some controllers redirect to the #show action, others to #index. The problem is that when expecting a redirect to #show, AFAIK, we have to know the record ID in order to build the expected URL. And we don't know the record ID within the controller spec. We only know it within the shared examples. So how can we pass an expected redirect URL from the controller spec to the shared example if we do not yet know what that URL is (because we don't know the record ID)?
Also, please let me know if you spot any additional issues.
The controller spec:
# spec/controllers/quotes_controller_spec.rb
require "rails_helper"
RSpec.describe QuotesController, :focus, :type => :controller do
login_admin
let(:model) { Quote }
let(:record) { FactoryGirl.create(:quote) }
let(:records) { FactoryGirl.create_pair(:quote) }
let(:valid_attributes) { FactoryGirl.attributes_for(:quote, quote: "New quote") }
let(:invalid_attributes) { valid_attributes.update(quote: nil) }
include_examples "GET #index"
include_examples "GET #show"
include_examples "GET #new"
include_examples "GET #edit"
include_examples "POST #create", "quote_path(assigns(:quote))"
include_examples "PATCH #update", "quote_url"
include_examples "DELETE #destroy", "quotes_url"
end
The shared examples:
# spec/support/shared_examples/controller_restful_actions.rb
def ivar_name(model, plural: false)
if plural
model.name.pluralize.underscore.to_sym
else
model.name.underscore.to_sym
end
end
def record_name(model)
model.name.underscore.to_sym
end
RSpec.shared_examples "GET #index" do
describe "GET #index" do
it "requires login" do
sign_out current_user
get :index
expect(response).to require_login
end
it "enforces authorization" do
get :index
expect(controller).to enforce_authorization
end
it "populates instance variable with an array of records" do
get :index
expect(assigns(ivar_name(model, plural: true))).to match_array(records)
end
end
end
RSpec.shared_examples "GET #show" do
describe "GET #show" do
it "requires login" do
sign_out current_user
get :show, id: record
expect(response).to require_login
end
it "enforces authorization" do
get :show, id: record
expect(controller).to enforce_authorization
end
it "assigns the requested record to an instance variable" do
get :show, id: record
expect(assigns(ivar_name(model))).to eq(record)
end
end
end
RSpec.shared_examples "GET #new" do
describe "GET #new" do
it "requires login" do
sign_out current_user
get :new
expect(response).to require_login
end
it "enforces authorization" do
get :new
expect(controller).to enforce_authorization
end
it "assigns a new record to an instance variable" do
get :new
expect(assigns(ivar_name(model))).to be_a_new(model)
end
end
end
RSpec.shared_examples "GET #edit" do
describe "GET #edit" do
let(:record) { FactoryGirl.create(factory_name(model)) }
it "requires login" do
sign_out current_user
get :edit, id: record
expect(response).to require_login
end
it "enforces authorization" do
get :edit, id: record
expect(controller).to enforce_authorization
end
it "assigns the requested record to an instance variable" do
get :edit, id: record
expect(assigns(ivar_name(model))).to eq(record)
end
end
end
RSpec.shared_examples "POST #create" do |redirect_path_helper|
describe "POST #create" do
it "requires login" do
sign_out current_user
post :create, { record_name(model) => valid_attributes }
expect(response).to require_login
end
it "enforces authorization" do
post :create, { record_name(model) => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "saves the new record in the database" do
expect{
post :create, { record_name(model) => valid_attributes }
}.to change(model, :count).by(1)
end
it "assigns a newly created but unsaved record to an instance variable" do
post :create, { record_name(model) => valid_attributes }
expect(assigns(ivar_name(model))).to be_a(model)
expect(assigns(ivar_name(model))).to be_persisted
end
it "redirects to #{redirect_path_helper}" do
post :create, { record_name(model) => valid_attributes }
expect(response).to redirect_to(eval(redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not save the new record in the database" do
expect{
post :create, { record_name(model) => invalid_attributes }
}.not_to change(model, :count)
end
it "assigns a newly created but unsaved record an instance variable" do
post :create, { record_name(model) => invalid_attributes }
expect(assigns(ivar_name(model))).to be_a_new(model)
end
it "re-renders the :new template" do
post :create, { record_name(model) => invalid_attributes }
expect(response).to render_template(:new)
end
end
end
end
RSpec.shared_examples "PATCH #update" do |redirect_path_helper|
describe "PATCH #update" do
let(:record) { FactoryGirl.create(factory_name(model)) }
it "requires login" do
sign_out current_user
patch :update, { :id => record, record_name(model) => valid_attributes }
expect(response).to require_login
end
it "enforces authorization" do
patch :update, { :id => record, record_name(model) => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "updates the requested record" do
patch :update, { :id => record, record_name(model) => valid_attributes }
record.reload
expect(record).to have_attributes(valid_attributes)
end
it "assigns the requested record to an instance variable" do
put :update, { :id => record, record_name(model) => valid_attributes }
expect(assigns(ivar_name(model))).to eq(record)
end
it "redirects to #{redirect_path_helper}" do
patch :update, { :id => record, record_name(model) => valid_attributes }
expect(response).to redirect_to(eval(redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not update the requested record" do
expect {
patch :update, { :id => record, record_name(model) => invalid_attributes }
}.not_to change { record.reload.attributes }
end
it "assigns the record to an instance variable" do
patch :update, { :id => record, record_name(model) => invalid_attributes }
expect(assigns(ivar_name(model))).to eq(record)
end
it "re-renders the :edit template" do
patch :update, { :id => record, record_name(model) => invalid_attributes }
expect(response).to render_template(:edit)
end
end
end
end
RSpec.shared_examples "DELETE #destroy" do |redirect_path_helper|
describe "DELETE #destroy" do
it "requires login" do
sign_out current_user
delete :destroy, id: record
expect(response).to require_login
end
it "enforces authorization" do
delete :destroy, id: record
expect(controller).to enforce_authorization
end
it "deletes the record" do
# Records are lazily created. Here we must force its creation.
record
expect{
delete :destroy, id: record
}.to change(model, :count).by(-1)
end
it "redirects to #{redirect_path_helper}" do
delete :destroy, id: record
expect(response).to redirect_to(eval(redirect_path_helper))
end
end
end
Probably not an answer but too long for a comment:
First of all you can wrap all of those in a shared_examples_for block e.g.
shared_examples_for 'a CRUD Controller' do
context "GET #index" do
it "requires login" do
sign_out current_user
get :index
expect(response).to require_login
end
####
end
context "GET #show" do
it "requires login" do
sign_out current_user
get :show, id: record
expect(response).to require_login
end
####
end
end
Secondly You can have shared examples inside shared examples to the above can be
shared_examples_for 'a CRUD Controller' do
shared_examples_for 'authenticatable' do |view:,params:{}|
it "requires login" do
sign_out current_user
get view, **params
expect(response).to require_login
end
end
context "GET #index" do
it_behaves_like 'authenticatable', view: :index
####
end
context "GET #show" do
it_behaves_like 'authenticatable', view: :show, id: record
####
end
end
Third you can assign variables inside a it_behaves_like block eg.
RSpec.describe QuotesController, :focus, :type => :controller do
login_admin
it_behaves_like 'a CRUD Controller' do
let(:model) { Quote }
let(:record) { FactoryGirl.create(:quote) }
let(:records) { FactoryGirl.create_pair(:quote) }
let(:valid_attributes) { FactoryGirl.attributes_for(:quote, quote: "New quote") }
let(:invalid_attributes) { valid_attributes.update(quote: nil) }
end
end
Fourth this too can be simplified
shared_examples_for 'a CRUD Controller' do |model:|
singular,plural = 2.times.map { |n| model.name.pluralize(n).underscore.to_sym }
let(:record) { FactoryGirl.create(singular)
let(:records) {FactoryGirl.create_pair(singular) }
let(:valid_attributes) do
# build should create the nested associations correctly as long
# as your factories are right
FactoryGirl.build(singular).attributes.delete_if do |k,_|
# this is because ActiveRecord#attributes contains columns
# you don't want to be considered updateable
["id","created_at","updated_at"].include?(k)
end
end
let(:invalid_attributes) do
# create an :invalid trait in your factory so that
# you don't have to worry about the model
FactoryGirl.build(singular, :invalid).attributes.delete_if do |k,_|
["id","created_at","updated_at"].include?(k)
end
end
####
end
RSpec.describe QuotesController, :focus, :type => :controller do
login_admin
it_behaves_like 'a CRUD Controller', model: Quote
end
Finally you are going to find that using a memoized let! will help drastically since you are creating an extraordinary amount of records in those tests as it stands now. This will degrade performance drastically and if you get to a model that has certain globally unique attributes your tests will fail everywhere.
Hopefully this helps start pointing you in the right direction
Update to control testing actions
shared_examples_for 'a CRUD Controller' do |model:|
accessible_method = ->(meth) { public_methods.include?(meth) }
context "GET #index", if: controller.method_defined?(:index) do
it_behaves_like 'authenticatable', view: :index
####
end
context "GET #show", if: controller.method_defined?(:show) do
it_behaves_like 'authenticatable', view: :show, id: record
####
end
end
Here is the improved code (based on engineersmnky's suggestions). Any suggestions for further improvements are welcome.
Controller spec:
# spec/controllers/quotes_controller_spec.rb
require "rails_helper"
RSpec.describe QuotesController, :type => :controller do
it_behaves_like "a CRUD controller",
model: Quote,
create_redirect_path_helper: "quote_path(assigns(:quote))",
update_redirect_path_helper: "quote_url",
delete_redirect_path_helper: "quotes_url"
end
Shared examples:
# spec/support/shared_examples/controller_restful_actions.rb
RSpec.shared_examples "a CRUD controller" do |model:,
create_redirect_path_helper:,
update_redirect_path_helper:,
delete_redirect_path_helper:|
def self.controller_has_action?(action)
described_class.action_methods.include?(action.to_s)
end
resource_singular = model.name.underscore.to_sym
resource_plural = model.name.pluralize.underscore.to_sym
before(:each) { login_admin }
let(:record) { FactoryGirl.create(resource_singular) }
let(:records) { FactoryGirl.create_pair(resource_singular) }
# Models that validate the presence of associated records require some
# hacking in the factory to include associations in the attributes_for output.
let(:valid_attributes) { FactoryGirl.attributes_for(resource_singular) }
# All factories must have a trait called :invalid
let(:invalid_attributes) do
FactoryGirl.attributes_for(resource_singular, :invalid)
end
describe "GET #index", if: controller_has_action?(:index) do
it "requires login" do
logout
get :index
expect(response).to require_login_web
end
it "enforces authorization" do
get :index
expect(controller).to enforce_authorization
end
it "populates ##{resource_plural} with an array of #{resource_plural}" do
# Force records to be created before the request.
records
get :index
# Required when testing the User model, or else the user created
# by the Devise login helper skews the result of this test.
expected_records = assigns(resource_plural) - [#current_user]
expect(expected_records).to match_array(records)
end
end
describe "GET #show", if: controller_has_action?(:show) do
it "requires login" do
logout
get :show, id: record
expect(response).to require_login_web
end
it "enforces authorization" do
get :show, id: record
expect(controller).to enforce_authorization
end
it "assigns the requested #{resource_singular} to an instance variable" do
get :show, id: record
expect(assigns(resource_singular)).to eq(record)
end
end
describe "GET #new", if: controller_has_action?(:new) do
it "requires login" do
logout
get :new
expect(response).to require_login_web
end
it "enforces authorization" do
get :new
expect(controller).to enforce_authorization
end
it "assigns a new #{resource_singular} to ##{resource_singular}" do
get :new
expect(assigns(resource_singular)).to be_a_new(model)
end
end
describe "GET #edit", if: controller_has_action?(:edit) do
it "requires login" do
logout
get :edit, id: record
expect(response).to require_login_web
end
it "enforces authorization" do
get :edit, id: record
expect(controller).to enforce_authorization
end
it "assigns #{resource_singular} to ##{resource_singular}" do
get :edit, id: record
expect(assigns(resource_singular)).to eq(record)
end
end
describe "POST #create", if: controller_has_action?(:create) do
it "requires login" do
logout
post :create, { resource_singular => valid_attributes }
expect(response).to require_login_web
end
it "enforces authorization" do
post :create, { resource_singular => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "saves the new #{resource_singular} in the database" do
expect{
post :create, { resource_singular => valid_attributes }
}.to change(model, :count).by(1)
end
it "assigns the saved #{resource_singular} to ##{resource_singular}" do
post :create, { resource_singular => valid_attributes }
expect(assigns(resource_singular)).to be_an_instance_of(model)
expect(assigns(resource_singular)).to be_persisted
end
it "redirects to #{create_redirect_path_helper}" do
post :create, { resource_singular => valid_attributes }
expect(response).to redirect_to(eval(create_redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not save the new #{resource_singular} in the database" do
expect{
post :create, { resource_singular => invalid_attributes }
}.not_to change(model, :count)
end
it "assigns the unsaved #{resource_singular} to ##{resource_singular}" do
post :create, { resource_singular => invalid_attributes }
expect(assigns(resource_singular)).to be_a_new(model)
end
it "re-renders the :new template" do
post :create, { resource_singular => invalid_attributes }
expect(response).to render_template(:new)
end
end
end
describe "PATCH #update", if: controller_has_action?(:update) do
it "requires login" do
logout
patch :update, { :id => record,
resource_singular => valid_attributes }
expect(response).to require_login_web
end
it "enforces authorization" do
patch :update, { :id => record,
resource_singular => valid_attributes }
expect(controller).to enforce_authorization
end
context "with valid attributes" do
it "updates the requested #{resource_singular}" do
patch :update, { :id => record,
resource_singular => valid_attributes }
record.reload
# Required when testing Devise's User model with reconfirmable on
record.try(:confirm)
expect(record).to have_attributes(valid_attributes)
end
it "assigns the #{resource_singular} to ##{resource_singular}" do
put :update, { :id => record,
resource_singular => valid_attributes }
expect(assigns(resource_singular)).to eq(record)
end
it "redirects to #{update_redirect_path_helper}" do
patch :update, { :id => record,
resource_singular => valid_attributes }
expect(response).to redirect_to(eval(update_redirect_path_helper))
end
end
context "with invalid attributes" do
it "does not update the #{resource_singular}" do
# Do not attempt to "refactor" the following to any of the following:
# not_to change { quote }
# not_to change { quote.attributes }
# not_to have_attributes(invalid_attributes)
# None of the above will work. See
# https://github.com/rspec/rspec-expectations/issues/996#issuecomment-310729685
expect {
patch :update, { :id => record,
resource_singular => invalid_attributes }
}.not_to change { record.reload.attributes }
end
it "assigns the #{resource_singular} to ##{resource_singular}" do
patch :update, { :id => record,
resource_singular => invalid_attributes }
expect(assigns(resource_singular)).to eq(record)
end
it "re-renders the :edit template" do
patch :update, { :id => record,
resource_singular => invalid_attributes }
expect(response).to render_template(:edit)
end
end
end
describe "DELETE #destroy", if: controller_has_action?(:destroy) do
it "requires login" do
logout
delete :destroy, id: record
expect(response).to require_login_web
end
it "enforces authorization" do
delete :destroy, id: record
expect(controller).to enforce_authorization
end
it "deletes the #{resource_singular}" do
# Force record to be created before the `expect` block.
# Otherwise, it is both created and deleted INSIDE the block, causing the
# count not to change.
record
expect{
delete :destroy, id: record
}.to change(model, :count).by(-1)
end
it "redirects to #{delete_redirect_path_helper}" do
delete :destroy, id: record
expect(response).to redirect_to(eval(delete_redirect_path_helper))
end
end
end
For the let blocks, does it not work if you pass in the model as a parameter to the shared example like you do with the redirect_path_helper?
include_examples "GET #index", Quote
and then in your shared_example you can use the record_name method to create record and records from FactoryGirl and generate valid_attributes and invalid_attributes (you could create an :invalid_quote factory as well for invalid attributes, not sure if that's considered a good practice/idea with FactoryGirl though) from there.
For the second problem, you don't need to use the named route helpers, url_for(controller: :quote) and url_for(#quote) should both work.
With which matcher and how can I test if the #post_comment and #post_comment.user is properly assigned?
expect(assigns(:post_comment)).to be_a_new(PostComment) is not working here.
UPDATE:
With the following setup I also get the following error. What should I change to be able to test the invalid attrs?
Posts::PostCommentsController when user is logged in POST create with invalid attributes doesn't save the new product in the db
Failure/Error: #post_comment.save!
ActiveRecord::RecordInvalid:
Validation failed: Body can't be blank
IF I delete #post_comment.save! then I get
Posts::PostCommentsController when user is logged in POST create with invalid attributes doesn't save the new product in the db
Failure/Error: <span class="post-comment-updated"><%= local_time_ago(post_comment.updated_at) %></span>
ActionView::Template::Error:
undefined method `to_time' for nil:NilClass
post_comments_controller
def create
#post = Post.find(params[:post_id])
#post_comment = #post.post_comments.build(post_comment_params)
authorize #post_comment
#post_comment.user = current_user
#post_comment.save!
if #post_comment.save
#post.send_post_comment_creation_notification(#post_comment)
#post_comment_reply = PostCommentReply.new
respond_to do |format|
format.html { redirect_to posts_path, notice: "Comment saved!" }
format.js
end
end
end
post_comments_controller_spec.rb
describe "POST create" do
let!(:profile) { create(:profile, user: #user) }
let!(:post_instance) { create(:post, user: #user) }
context "with valid attributes" do
subject(:create_action) { xhr :post, :create, post_id: post_instance.id, post_comment: attributes_for(:post_comment, post_id: post_instance.id, user: #user) }
it "saves the new task in the db" do
expect{ create_action }.to change{ PostComment.count }.by(1)
end
it "assigns instance variables" do
create_action
expect(assigns(:post)).to eq(post_instance)
#########How to test these two?
#expect(assigns(:post_comment)).to be_a_new(PostComment)
#expect(assigns(:post_comment.user)).to eq(#user)
expect(assigns(:post_comment_reply)).to be_a_new(PostCommentReply)
end
it "assigns all the instance variables"
it "responds with success" do
create_action
expect(response).to have_http_status(200)
end
end
context "with invalid attributes" do
subject(:create_action) { xhr :post, :create, post_id: post_instance.id, post_comment: attributes_for(:post_comment, post_id: post_instance.id, user: #user, body: "") }
it "doesn't save the new product in the db" do
expect{ create_action }.to_not change{ PostComment.count }
end
end
end
How to test these two?
expect(assigns(:post_comment)).to be_a_new(PostComment)
expect(assigns(:post_comment.user)).to eq(#user)
I believe you shoudl test not a new record, but a record of a class, and persisted record:
expect(assigns(:post_comment)).to be_a(PostComment)
expect(assigns(:post_comment)).to be_presisted
expect(assigns(:post_comment.user)).to eq(#user)
Excessive code.
#post_comment.save!
if #post_comment.save
You shall to keep only the single record of that, I believe it is enough save with exception:
#post_comment.save!
So other part code you can pick out of if block. Exception from save! you shall to trap with rescue_from.
I have the following failure:
Failures:
1) RelationshipsController creating a relationship with Ajax should increment the Relationship count
Failure/Error: xhr :post, :create, relationship: { followed_id: other_user.id }
NoMethodError:
undefined method `authenticate!' for nil:NilClass
# ./spec/controllers/relationships_controller_spec.rb:14:in `block (4 levels) in <top (required)>'
# ./spec/controllers/relationships_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
But it is very strange, if I visit the site, the thing works (the followers counter does increment if I click the Follow button:
And the weirdest thing is that there isn't any authenticate! method in relationships_controller_spec.rb:
require 'spec_helper'
describe RelationshipsController do
let(:user) { FactoryGirl.create(:user) }
let(:other_user) { FactoryGirl.create(:user) }
before { sign_in user }
describe "creating a relationship with Ajax" do
it "should increment the Relationship count" do
expect do
xhr :post, :create, relationship: { followed_id: other_user.id }
end.to change(Relationship, :count).by(1)
end
it "should respond with success" do
xhr :post, :create, relationship: { followed_id: other_user.id }
response.should be_success
end
end
describe "destroying a relationship with Ajax" do
before { user.follow!(other_user) }
let(:relationship) { user.relationships.find_by_followed_id(other_user) }
it "should decrement the Relationship count" do
expect do
xhr :delete, :destroy, id: relationship.id
end.to change(Relationship, :count).by(-1)
end
it "should respond with success" do
xhr :delete, :destroy, id: relationship.id
response.should be_success
end
end
end
Neither in the controller:
class RelationshipsController < ApplicationController
before_filter :authenticate_user!
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
What could be the problem?
(By the way, these test were made following the Ruby on Rails Tutorial. After that, I removed all the authentication system because I wanted to use Devise.)
Setting config.include Devise::TestHelpers, :type => :controller no longer works in later versions of Devise. What you need to do is log out an anonymous user to set up the proper variables:
before :each do
sign_out :user
end
I had to add this:
spec_helpers.rb:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
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?