Метод уничтожения 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)