Метод уничтожения RSpec (Rails Tutorial 3.2 Ch. 9, пример 10)

Примечание. Я прочитал этот вопрос и ответ, но по какой-то причине код не работает для меня. (см. ниже для ошибки, которую я получаю)

Упражнение 10 из Главы 9 Учебного курса Rails предлагает вам: Изменить действие уничтожения [для пользователей], чтобы пользователи администраторов не уничтожали себя. (Сначала напишите тест.)

Трудная часть здесь - это тестирование, потому что приложение уже скрывает ссылку "удалить" для текущего пользователя, поэтому вам нужно напрямую выполнить HTTP-запрос.

Я получил код и протестировал его, удалив фрагмент кода, который скрывает ссылку удаления для текущего пользователя. Разумеется, если я нажму ссылку на удаление для текущего зарегистрированного пользователя, он перенаправляет меня и дает мне уведомление.

От users_controller.rb

  def destroy
    @user = User.find(params[:id])
    if current_user?(@user)
      redirect_to users_path, notice: "You can't destroy yourself."
    else
      @user.destroy
      flash[:success] = "User destroyed."
      redirect_to users_path
    end
  end

Проблема, с которой я столкнулась, заключается в написании тестов для этого, которые отправят запрос на удаление и вызовет метод destroy. Я попробовал решение из Rspec test для уничтожить, если не удалить ссылку, которую я копирую здесь:

Из user_pages_spec.rb

 describe "destroy" do
    let(:admin) { FactoryGirl.create(:admin) }

    it "should not allow the admin to delete herself" do
      sign_in admin
      #expect { delete user_path(admin), method: :delete }.should change(User, :count)
      expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
    end
  end

Но когда я запускаю это, я получаю эту ошибку от RSpec

Failures:

  1) User Pages destroy should not allow the admin to delete herself
     Failure/Error: expect { delete :destroy, :id => admin.id }.should_not change(User, :count)
     ArgumentError:
       bad argument (expected URI object or URI string)
     # ./spec/requests/user_pages_spec.rb:180:in `block (4 levels) in <top (required)>'
     # ./spec/requests/user_pages_spec.rb:180:in `block (3 levels) in <top (required)>'

Итак, мои вопросы: 1) Почему этот код выше сбой? 2) Как смоделировать "удаление", чтобы вызвать действие destroy в моем контроллере?

Окружающая среда: Mac OS X ruby 1.9.3p194 Rails 3.2.3

Драгоценные камни для тестирования:
группа: test do   gem 'rspec-rails', '2.9.0'   gem 'capybara', '1.1.2'   gem 'rb-fsevent', '0.4.3.1',: require = > false   gem 'growl', '1.0.3'   gem 'guard-spork', '0.3.2'   gem 'spork', '0.9.0'   gem 'factory_girl_rails', '1.4.0' конец

Дополнительная информация Я попытался использовать тонну, чтобы попытаться имитировать щелчок по ссылке удаления, и ни один из них не работает. Я использую камень отладчика, чтобы узнать, вызван ли метод destroy. В тесте, который нажимает на ссылку для удаления другого пользователя, вызывается метод destroy, и он отлично работает:

it "should be able to delete another user" do
  expect { click_link('delete') }.to change(User, :count).by(-1)
end

Но ничто из того, что я пытался сгенерировать запрос удаления напрямую, не работал, чтобы вызвать метод destroy.

Спасибо за вашу помощь!

Воля

** ОБНОВЛЕНИЕ **

Я попробовал предложение DVG:

describe "destroy" do
    let(:admin) { FactoryGirl.create(:admin) }

    it "should not allow the admin to delete herself" do
      sign_in admin
      #expect { delete user_path(admin), method: :delete }.should change(User, :count)
      expect { delete :destroy, :id => admin }.to_not change(User, :count)
    end
  end

И получил эту ошибку:

6) User Pages destroy should not allow the admin to delete herself
     Failure/Error: expect { delete :destroy, :id => admin }.to_not change(User, :count)
     ArgumentError:
       bad argument (expected URI object or URI string)
     # ./spec/requests/user_pages_spec.rb:190:in `block (4 levels) in <top (required)>'
     # ./spec/requests/user_pages_spec.rb:190:in `block (3 levels) in <top (required)>'

Решение

Я понял это после НАВСЕГДА.

Мне пришлось использовать Rack:: Test для запроса DELETE, но Capybara и Rack:: Test не используют одно и то же MockSession, поэтому мне пришлось вытащить файлы cookie: remember_token и:! sample_app_session и поместить их в запрос DELETE вручную. Вот что сработало. (другая проблема, которую я имел, перечисленная ниже, заключалась в том, что у меня было выражение force_ssl, которое не позволяло вызвать действие destroy.

describe "destroy" do
    let!(:admin) { FactoryGirl.create(:admin) }

    before do
      sign_in admin
    end

    it "should delete a normal user" do
      user = FactoryGirl.create(:user)
      expect { delete user_path(user), {},
       'HTTP_COOKIE' => "remember_token=#{admin.remember_token},
        #{Capybara.current_session.driver.response.headers["Set-Cookie"]}" }.
        to change(User, :count).by(-1)
    end

    it "should not allow the admin to delete herself" do
      expect { delete user_path(admin), {},
       'HTTP_COOKIE' => "remember_token=#{admin.remember_token},
        #{Capybara.current_session.driver.response.headers["Set-Cookie"]}" }.
       to_not change(User, :count)
    end
  end

У меня был оператор force_ssl после моего before_filters в моем users_controller.rb, и это как-то отбросило вещи, так что я никогда не попадал в действие destroy.

class UsersController < ApplicationController
  before_filter :signed_in_user,  only: [:edit, :update, :index]
  before_filter :existing_user,   only: [:new, :create]
  before_filter :correct_user,    only: [:edit, :update]
  before_filter :admin_user,      only: :destroy

  #force_ssl

  def index
    @users = User.paginate(page: params[:page])
  end

  def show 
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
  end

  def destroy
    @user = User.find(params[:id])
    if current_user?(@user)
      redirect_to users_path, notice: "You can't destroy yourself."
    else
      @user.destroy
      flash[:success] = "User destroyed."
      redirect_to users_path
    end
  end

Это было полезно при доступе к решению

https://gist.github.com/484787

http://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles/

Ответы

Ответ 1

Вы вводите в заблуждение спецификаторы запросов rspec-rails, которые являются интеграционными тестами и выполняются в моделируемых браузерах и спецификациях контроллера, которые изолируют тестовый контроллер. delete(action, *args)get, post и т.д.) - это метод, имитирующий запрос от ActionController:: TestCase, поэтому он не доступен в вашем тесте.

Таким образом, ваш единственный вариант - имитировать щелчок в браузере. Я не знаю, как вы прячете ссылку на удаление, если html есть, но скрытый, вы должны иметь возможность щелкнуть его. Если он не существует (удаляется на стороне сервера при создании представления), вы можете использовать capybara page.execute_script (но вы должны включить javascript для этого примера :js => true). Вы можете либо добавить ссылку назад:

page.execute_script("$('body').append("<a href="/users/1" data-method="delete" rel="nofollow">Destroy</a>")")

или выполните вызов ajax:

page.execute_script("$.ajax({type:'DELETE',url:'/users/1'})")

Не тестировалось, но что-то вроде этого должно работать.

Ответ 2

Я решил эту же проблему, используя следующее:

describe "should not be able to delete themselves" do
  it { expect { delete user_path(admin) }.not_to change(User, :count) }
end

Ответ 3

Решение CallumD работало для меня и казалось наиболее совместимым с техникой, рекомендованной в остальном учебнике Майкла Хартла. Но я хотел немного подтянуть синтаксис, чтобы сделать его более совместимым с другими спецификациями в одном и том же учебнике:

it "should not be able to delete itself" do
  expect { delete user_path(admin) }.not_to change(User, :count)
end

Ответ 4

Вот что я закончил (Rspec 3.2):

describe 'DELETE destroy' do
  before :each do
    delete :destroy, { id: current_partner_role }
  end

  it 'destroys role' do
    expect(assigns(:role).destroyed?).to be true
  end

"уничтожен?" сам метод специфицирован Rails, поэтому ИМХО должно быть в порядке, чтобы полагаться на него.

https://github.com/rails/rails/blob/5142d5411481c893f817c1431b0869be3745060f/activerecord/lib/active_record/persistence.rb#L91

Ответ 5

Попробуйте следующее:

expect { delete :destroy, :id => admin }.to_not change(User, :count)