Подтвердить что ты не робот

Как протестировать Консоль контроллера в Rails 4

Каков наилучший способ справиться с тестированием проблем при использовании в контроллерах Rails 4? Скажем, у меня есть тривиальная проблема Citations.

module Citations
    extend ActiveSupport::Concern
    def citations ; end
end

Ожидаемое поведение при тестировании заключается в том, что любой контроллер, который включает эту проблему, получит эту конечную точку Citations.

class ConversationController < ActionController::Base
    include Citations
end

Simple.

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 setup method.
4b9b3361

Ответ 1

Вы найдете много советов, которые рассказывают вам использовать общие примеры и запускать их в рамках ваших включенных контроллеров.

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

Метод 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

Упрощение метода 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 и установить тип в случае, если это не произойдет по умолчанию.

Ответ 3

Мой ответ может выглядеть немного сложнее, чем у @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. В этом случае довольно часто копировать большой объем контроллеров при создании новой версии, и это ужасно легко сделать такую ​​тонкую ошибку копирования-пасты.

Ответ 4

Я использую более простой способ проверить свои проблемы с контроллером, не уверен, что это правильный путь, но, казалось, намного проще, чем выше, и имеет смысл для меня, его вид использования объема ваших включенных контроллеров. Пожалуйста, дайте мне знать, есть ли какие-либо проблемы с этим методом. Контроллер образца:

class MyController < V1::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)    { V1::AppointmentsController.new }
  context '#column_type' do
    it 'should return :phone for phone type column' do
      expect(ac.column_type('phone_daytime')).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