Как протестировать проблему контроллера в Rails 4
-
21-12-2019 - |
Вопрос
Как лучше всего справиться с тестированием проблем при использовании в контроллерах Rails 4?Скажем, у меня есть тривиальная проблема Citations
.
module Citations
extend ActiveSupport::Concern
def citations ; end
end
Ожидаемое поведение при тестировании заключается в том, что любой контроллер, включающий эту проблему, получит это citations
конечная точка.
class ConversationController < ActionController::Base
include Citations
end
Простой.
ConversationController.new.respond_to? :yelling #=> true
Но как правильно проверить эту проблему изолированно?
class CitationConcernController < ActionController::Base
include Citations
end
describe CitationConcernController, type: :controller do
it 'should add the citations endpoint' do
get :citations
expect(response).to be_successful
end
end
К сожалению, это не удается.
CitationConcernController
should add the citations endpoint (FAILED - 1)
Failures:
1) CitationConcernController should add the citations endpoint
Failure/Error: get :citations
ActionController::UrlGenerationError:
No route matches {:controller=>"citation_concern", :action=>"citations"}
# ./controller_concern_spec.rb:14:in `block (2 levels) in <top (required)>'
Это надуманный пример.В моем приложении я получаю другую ошибку.
RuntimeError:
@routes is nil: make sure you set it in your test's setup method.
Решение
Вы найдете множество советов, советующих вам использовать общие примеры и запускать их в рамках включенных вами контроллеров.
Лично я считаю это излишним и предпочитаю выполнять модульное тестирование изолированно, а затем использовать интеграционное тестирование для подтверждения поведения моих контроллеров.
Метод 1:без маршрутизации или тестирования ответа
Создайте поддельный контроллер и протестируйте его методы:
describe MyControllerConcern do
before do
class FakesController < ApplicationController
include MyControllerConcern
end
end
after { Object.send :remove_const, :FakesController }
let(:object) { FakesController.new }
describe 'my_method_to_test' do
it { expect(object).to eq('expected result') }
end
end
Метод 2:реакция тестирования
Когда ваша проблема связана с маршрутизацией или вам нужно проверить ответ, рендеринг и т. д.вам нужно запустить тест с анонимным контроллером.Это позволит вам получить доступ ко всем методам и помощникам rspec, связанным с контроллером:
describe MyControllerConcern, type: :controller do
controller(ApplicationController) do
include MyControllerConcern
def fake_action; redirect_to '/an_url'; end
end
before { routes.draw {
get 'fake_action' => 'anonymous#fake_action'
} }
describe 'my_method_to_test' do
before { get :fake_action }
it { expect(response).to redirect_to('/an_url') }
end
end
Вы можете видеть, что нам нужно обернуть анонимный контроллер в controller(ApplicationController)
.Если ваши классы унаследованы от другого класса, чем ApplicationController
, вам нужно будет адаптировать это.
Также, чтобы это работало правильно, вы должны объявить в своем spec_helper.rb файл:
config.infer_base_class_for_anonymous_controllers = true
Примечание:продолжайте проверять, включена ли ваша проблема
Также важно проверить, включен ли ваш проблемный класс в ваши целевые классы, достаточно одной строки:
describe SomeTargetedController do
describe 'includes MyControllerConcern' do
it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
end
end
Другие советы
Упрощение метода 2 из ответа, получившего наибольшее количество голосов.
Я предпочитаю anonymous controller
поддерживается в rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller
Вы будете делать:
describe ApplicationController, type: :controller do
controller do
include MyControllerConcern
def index; end
end
describe 'GET index' do
it 'will work' do
get :index
end
end
end
Обратите внимание, что вам необходимо описать ApplicationController
и задайте тип на случай, если по умолчанию этого не происходит.
Мой ответ может показаться немного более сложным, чем ответы @Benj и @Calin, но у него есть свои преимущества.
describe Concerns::MyConcern, type: :controller do
described_class.tap do |mod|
controller(ActionController::Base) { include mod }
end
# your tests go here
end
Прежде всего, я рекомендую использовать анонимный контроллер, который является подклассом ActionController::Base
, не ApplicationController
ни один другой базовый контроллер не определен в вашем приложении.Таким образом, вы сможете протестировать проблему изолированно от любого из ваших контроллеров.Если вы ожидаете, что какие-то методы будут определены в базовом контроллере, просто заглушите их.
Кроме того, рекомендуется избегать повторного ввода имени соответствующего модуля, поскольку это помогает избежать ошибок копирования-вставки.К сожалению, described_class
недоступен в блоке, переданном controller(ActionController::Base)
, поэтому я использую #tap
способ создания другой привязки, которая хранит described_class
в локальной переменной.Это особенно важно при работе с версионными API.В таком случае довольно часто копируется большой объем контроллеров при создании новой версии, и тогда ужасно легко допустить такую тонкую ошибку копирования-вставки.
Я использую более простой способ проверить проблемы с моим контроллером, не уверен, что это правильно, но казалось намного проще, что вышеперечисленное и имеет смысл для меня, своего рода используя объем ваших включенных контроллеров.Пожалуйста, дайте мне знать, если есть какие-либо проблемы с этим методом. Образец контроллера:
class MyController < BaseController
include MyConcern
def index
...
type = column_type(column_name)
...
end
.
конец
Мой контроллер. Концерн:
module MyConcern
...
def column_type(name)
return :phone if (column =~ /phone/).present?
return :id if column == 'id' || (column =~ /_id/).present?
:default
end
...
end
.
Spec Test для беспокойства:
require 'spec_helper'
describe SearchFilter do
let(:ac) { MyController.new }
context '#column_type' do
it 'should return :phone for phone type column' do
expect(ac.column_type('phone')).to eq(:phone)
end
it 'should return :id for id column' do
expect(ac.column_type('company_id')).to eq(:id)
end
it 'should return :id for id column' do
expect(ac.column_type('id')).to eq(:id)
end
it 'should return :default for other types of columns' do
expect(ac.column_type('company_name')).to eq(:default)
end
end
end
.