Pergunta

Qual é a melhor maneira de lidar com testes de preocupações quando usados ​​em controladores Rails 4?Digamos que eu tenha uma preocupação trivial Citations.

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

O comportamento esperado sob teste é que qualquer controlador que inclua esta preocupação receba esta citations ponto final.

class ConversationController < ActionController::Base
    include Citations
end

Simples.

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

Mas qual é a maneira correta de testar essa preocupação isoladamente?

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

Infelizmente, isso falha.

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

Este é um exemplo inventado.No meu aplicativo, recebo um erro diferente.

RuntimeError:
  @routes is nil: make sure you set it in your test's setup method.
Foi útil?

Solução

Você encontrará muitos conselhos para usar exemplos compartilhados e executá-los no escopo dos controladores incluídos.

Pessoalmente, acho isso um exagero e prefiro realizar testes unitários isoladamente e depois usar testes de integração para confirmar o comportamento dos meus controladores.

Método 1:sem roteamento ou teste de resposta

Crie um controlador falso e teste seus métodos:

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étodo 2:resposta de teste

Quando sua preocupação contém roteamento ou você precisa testar resposta, renderização, etc.você precisa executar seu teste com controlador anônimo.Isso permite que você obtenha acesso a todos os métodos e auxiliares rspec relacionados ao controlador:

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

Você pode ver que temos que envolver o controlador anônimo em um controller(ApplicationController).Se suas classes forem herdadas de outra classe que não ApplicationController, você precisará adaptar isso.

Além disso, para que isso funcione corretamente, você deve declarar em seu spec_helper.rb arquivo:

config.infer_base_class_for_anonymous_controllers = true

Observação:continue testando se sua preocupação está incluída

Também é importante testar se sua classe de interesse está incluída em suas classes alvo, uma linha é suficiente:

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

Outras dicas

Simplificando o método 2 da resposta mais votada.

Eu prefiro o anonymous controller suportado em rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller

Você vai fazer:

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

Observe que você precisa descrever o ApplicationController e defina o tipo caso isso não aconteça por padrão.

Minha resposta pode parecer um pouco mais complicada do que a de @Benj e @Calin, mas tem suas vantagens.

describe Concerns::MyConcern, type: :controller do

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

  # your tests go here
end

Primeiramente, recomendo o uso do controlador anônimo que é uma subclasse de ActionController::Base, não ApplicationController nem qualquer outro controlador base definido em seu aplicativo.Dessa forma, você poderá testar a preocupação isoladamente de qualquer um dos seus controladores.Se você espera que alguns métodos sejam definidos em um controlador base, basta fazer um stub deles.

Além disso, é uma boa ideia evitar redigitar o nome do módulo de preocupação, pois isso ajuda a evitar erros de copiar e colar.Infelizmente, described_class não está acessível em um bloco passado para controller(ActionController::Base), então eu uso #tap método para criar outra ligação que armazena described_class em uma variável local.Isto é especialmente importante ao trabalhar com APIs versionadas.Nesse caso, é bastante comum copiar grandes volumes de controladores ao criar uma nova versão, e é terrivelmente fácil cometer um erro tão sutil de copiar e colar.

Estou usando uma maneira mais simples de testar minhas preocupações com o controlador, não tenho certeza se essa é a maneira correta, mas parecia muito mais simples que a acima e faz sentido para mim, é como usar o escopo dos controladores incluídos.Por favor, deixe-me saber se houver algum problema com este método.controlador de amostra:

class MyController < BaseController
  include MyConcern

  def index
    ...

    type = column_type(column_name)
    ...
  end

fim

minha preocupação com o controlador:

module MyConcern
  ...
  def column_type(name)
    return :phone if (column =~ /phone/).present?
    return :id if column == 'id' || (column =~ /_id/).present?
   :default
  end
  ...

end

teste de especificação para preocupação:

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top