web-dev-qa-db-fra.com

Comment tester une préoccupation de contrôleur dans Rails 4

Quelle est la meilleure façon de gérer les tests de problèmes lorsqu'il est utilisé dans Rails 4 contrôleurs? Disons que j'ai une préoccupation triviale Citations.

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

Le comportement attendu sous test est que tout contrôleur qui inclut cette préoccupation obtiendrait ce point de terminaison citations.

class ConversationController < ActionController::Base
    include Citations
end

Facile.

ConversationController.new.respond_to? :yelling #=> true

Mais quelle est la bonne façon de tester cette préoccupation isolément?

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

Malheureusement, cela échoue.

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)>'

Ceci est un exemple artificiel. Dans mon application, j'obtiens une erreur différente.

RuntimeError:
  @routes is nil: make sure you set it in your test's setup method.
39
jelder

Vous trouverez de nombreux conseils vous indiquant d'utiliser des exemples partagés et de les exécuter dans le cadre de vos contrôleurs inclus.

Personnellement, je trouve cela excessif et je préfère effectuer des tests unitaires isolément, puis utiliser des tests d'intégration pour confirmer le comportement de mes contrôleurs.

Méthode 1: sans routage ni test de réponse

Créez un faux contrôleur et testez ses méthodes:

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

Méthode 2: tester la réponse

Lorsque votre problème contient un routage ou que vous devez tester la réponse, le rendu, etc., vous devez exécuter votre test avec un contrôleur anonyme. Cela vous permet d'accéder à toutes les méthodes et assistants rspec liés au contrôleur:

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

Vous pouvez voir que nous devons encapsuler le contrôleur anonyme dans une controller(ApplicationController). Si vos classes sont héritées d'une autre classe que ApplicationController, vous devrez l'adapter.

De plus, pour que cela fonctionne correctement, vous devez déclarer dans votre fichier spec_helper.rb :

config.infer_base_class_for_anonymous_controllers = true

Remarque: continuez à tester que votre préoccupation est incluse

Il est également important de tester que votre classe de préoccupation est incluse dans vos classes cibles, une seule ligne suffit:

describe SomeTargetedController do
  describe 'includes MyControllerConcern' do
    it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
  end
end
80
Benj

Simplification de la méthode 2 à partir de la réponse la plus votée.

Je préfère le anonymous controller pris en charge dans rspec http://www.relishapp.com/rspec/rspec-Rails/docs/controller-specs/anonymous-controller

Vous ferez:

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

Notez que vous devez décrire le ApplicationController et définir le type au cas où cela ne se produirait pas par défaut.

25
Calin

Ma réponse peut sembler un peu plus compliquée que celles de @Benj et @Calin, mais elle a ses avantages.

describe Concerns::MyConcern, type: :controller do

  described_class.tap do |mod|
    controller(ActionController::Base) { include mod }
  end

  # your tests go here
end

Tout d'abord, je recommande l'utilisation d'un contrôleur anonyme qui est une sous-classe de ActionController::Base, Pas ApplicationController ni aucun autre contrôleur de base défini dans votre application. De cette façon, vous pouvez tester le problème indépendamment de n'importe lequel de vos contrôleurs. Si vous vous attendez à ce que certaines méthodes soient définies dans un contrôleur de base, écrivez-les simplement.

De plus, c'est une bonne idée d'éviter de retaper le nom du module concerné car cela permet d'éviter les erreurs de copier-coller. Malheureusement, described_class N'est pas accessible dans un bloc passé à controller(ActionController::Base), donc j'utilise la méthode #tap Pour créer une autre liaison qui stocke described_class Dans une variable locale . Ceci est particulièrement important lorsque vous travaillez avec des API versionnées. Dans ce cas, il est assez courant de copier un grand volume de contrôleurs lors de la création d'une nouvelle version, et il est alors extrêmement facile de faire une erreur de copier-coller si subtile.

3
skalee