Andy Harvey
Andy Harvey

Reputation: 12653

Why does the second 'delete :destroy' in this spec not run?

I have a spec that is giving unexpected results. I've not been able to track down the cause. Can anyone help point me in the right direction?

let(:object1) { create :object }
let(:object2) { create :object }
let(:user)    { create :user }
describe "DELETE #destroy" do
  before :each do
    Rails.logger.info "Object 1 ID: #{object1.id}"
    Rails.logger.info "Object 2 ID: #{object4.id}"
    user.roles.push Role.where(name: 'FullAccess').first
    sign_in user
    delete :destroy, {:id => object1.to_param}
  end
  it {
     expect {
       delete :destroy, {:id => object2.to_param}
     }.to change(Object, :count).by(-1)
  }
end 

Results in

Failure/Error:
  expect {
    delete :destroy, {:id => object2.to_param}
  }.to change(Object, :count).by(-1)
expected #count to have changed by -1, but was changed by 0

But if I comment out delete in the before block, the test passes.

before :each do
  sign_in user
  # delete :destroy, {:id => office1.to_param}
end

Why would the second object not be deleted?

edit

The method being tested is

def ObjectController < ApplicationController
  load_and_authorize_resource
  def destroy
    Rails.logger.info "DELETE OBJECT ID: #{@object.id}" 
    @object.destroy
    respond_to do |format|
      format.html { redirect_to objects_url, notice: t('.notice') }
      format.json { head :no_content }
    end
  end
end

Edit 2

Added logging codes to the examples above. The log output now includes

Object 1 ID: 1
Object 2 ID: 2
DELETE OBJECT ID: 1 
DELETE OBJECT ID: 1 

Upvotes: 0

Views: 119

Answers (1)

Codebeef
Codebeef

Reputation: 43996

1. Lazy Evaluation

This is because your object2 is created and immediately destroyed within the expect block. let is lazy, let! is immediately evaluated

Change your let to let!, and it should work:

let!(:object1) { create :object }
let!(:object2) { create :object }
describe "DELETE #destroy" do
  before :each do
    sign_in user
    delete :destroy, {:id => object1.to_param}
  end
  it {
     expect {
       delete :destroy, {:id => object2.to_param}
     }.to change(Object, :count).by(-1)
  }
end

2. Memoized object

As for the other part of the problem, the reason you're seeing the spec fail is because the controller in the before block is the same instance as in the test. The object is being set only if it's not already set, so it won't get reset on your second call to the delete method (with different params). Something like this is happening:

@object ||= Object.find(...)

Removing the delete call to the controller action under test in the before block should fix this.

Upvotes: 3

Related Questions