Как написать тест контроллера двигателя Rails 3.1 в rspec?
Я написал движок Rails 3.1 с записями пространства имен. Следовательно, мои контроллеры находятся в приложениях/контроллерах/сообщениях/, моих моделях в приложениях/моделях/сообщениях и т.д. Я могу проверить модели просто отлично. Спецификация для одной модели выглядит как...
module Posts
describe Post do
describe 'Associations' do
it ...
end
... и все работает отлично.
Однако спецификации для контроллеров не работают. Двигатель Rails монтируется на/сообщениях, но контроллер - сообщения:: PostController. Таким образом, тесты рассматривают маршрут контроллера как сообщения/сообщения.
describe "GET index" do
it "assigns all posts as @posts" do
Posts::Post.stub(:all) { [mock_post] }
get :index
assigns(:posts).should eq([mock_post])
end
end
что дает...
1) Posts::PostsController GET index assigns all posts as @posts
Failure/Error: get :index
ActionController::RoutingError:
No route matches {:controller=>"posts/posts"}
# ./spec/controllers/posts/posts_controller_spec.rb:16
Я пробовал всевозможные трюки в файле маршрутов тестового приложения...: пространство имен и т.д., но безрезультатно.
Как мне сделать эту работу? Похоже, что этого не произойдет, поскольку двигатель помещает контроллер в/сообщения, но пространство имен помещает контроллер в /posts/posts для целей тестирования.
Ответы
Ответ 1
Я предполагаю, что вы тестируете свой движок с помощью приложения фиктивных рельсов, как тот, который будет создан enginex.
Ваш двигатель должен быть установлен в фиктивном приложении:
В spec/dummy/config/routes.rb
:
Dummy::Application.routes.draw do
mount Posts::Engine => '/posts-prefix'
end
Мое второе предположение заключается в том, что ваш двигатель изолирован:
В lib/posts.rb
:
module Posts
class Engine < Rails::Engine
isolate_namespace Posts
end
end
Я не знаю, действительно ли эти два предположения необходимы, но именно так структурирован мой собственный движок.
Обходной путь довольно прост, вместо этого
get :show, :id => 1
используйте этот
get :show, {:id => 1, :use_route => :posts}
Символом :posts
должно быть имя вашего движка и НЕ путь, на котором он монтируется.
Это работает, потому что параметры метода get передаются прямо на ActionDispatch::Routing::RouteSet::Generator#initialize
(определяется здесь), который, в свою очередь, использует @named_route
для получения правильный маршрут от Rack::Mount::RouteSet#generate
(здесь здесь и здесь).
Погружение в внутренние рельсы - это весело, но довольно много времени, я бы не делал этого каждый день;-).
НТН
Ответ 2
Я работал над этой проблемой, переопределяя предоставляемые get
, post
, put
и delete
методы, поэтому они всегда передают use_route
в качестве параметра.
В качестве основы для этого я использовал ответ Benoit. Спасибо, приятель!
module ControllerHacks
def get(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "GET")
end
# Executes a request simulating POST HTTP method and set/volley the response
def post(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "POST")
end
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "PUT")
end
# Executes a request simulating DELETE HTTP method and set/volley the response
def delete(action, parameters = nil, session = nil, flash = nil)
process_action(action, parameters, session, flash, "DELETE")
end
private
def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
parameters ||= {}
process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
end
end
RSpec.configure do |c|
c.include ControllerHacks, :type => :controller
end
Ответ 3
Используйте директиву rspec-rails routes
:
describe MyEngine::WidgetsController do
routes { MyEngine::Engine.routes }
# Specs can use the engine routes & named URL helpers
# without any other special code.
end
- Официальные документы RSpec Rails 2.14.
Ответ 4
На основе этого ответа я выбрал следующее решение:
#spec/spec_helper.rb
RSpec.configure do |config|
# other code
config.before(:each) { @routes = UserManager::Engine.routes }
end
Дополнительным преимуществом является то, что вам не нужно иметь блок before(:each)
в каждой спецификации контроллера.
Ответ 5
Решение проблемы, если вы не используете или не можете использовать isolate_namespace
:
module Posts
class Engine < Rails::Engine
end
end
В спецификациях контроллера исправить маршруты:
get :show, {:id => 1, :use_route => :posts_engine}
Rails добавляет _engine к вашим маршрутам приложения, если вы не используете isolate_namespace
.
Ответ 6
Я разрабатываю драгоценный камень для своей компании, который предоставляет API для приложений, которые мы запускаем. Мы все еще используем Rails 3.0.9, с последними Rspec-Rails (2.10.1). У меня была аналогичная проблема, когда я определил маршруты, подобные этому, в моем самолете Rails.
match '/companyname/api_name' => 'CompanyName/ApiName/ControllerName#apimethod'
Я получал ошибку, например
ActionController::RoutingError:
No route matches {:controller=>"company_name/api_name/controller_name", :action=>"apimethod"}
Оказывается, мне просто нужно было переопределить мой маршрут в нижнем ярлыке, чтобы RSpec мог его сопоставить.
match '/companyname/api_name' => 'company_name/api_name/controller_name#apimethod'
Я полагаю, что тесты контроллера Rspec используют обратный поиск, основанный на примере подчеркивания, тогда как Rails будет настраивать и интерпретировать маршрут, если вы определяете его в случае с верблюжьей или нижней линией.