Rspec, Rails: como testar métodos privados de controladores?

125

Eu tenho controlador:

class AccountController < ApplicationController
  def index
  end

  private
  def current_account
    @current_account ||= current_user.account
  end
end

Como testar o método privado current_accountcom rspec?

PS Eu uso Rspec2 e Ruby on Rails 3

petRUShka
fonte
8
Isso não responde à sua pergunta, mas os métodos privados não devem ser testados. Seus testes devem se importar apenas com a coisa real - sua API pública. Se seus métodos públicos funcionarem, os privados que eles chamam também funcionarão.
Samy Dindane
77
Discordo. É importante testar qualquer recurso suficientemente complexo no seu código.
precisa saber é o seguinte
11
Eu também discordo. Se sua API pública funcionar, você poderá assumir apenas que seus métodos privados estão funcionando conforme o esperado. Mas suas especificações podem estar passando por coincidência.
Rimian
4
Seria melhor extrair o método privado para uma nova classe que pode ser testada, se o método privado precisar de teste.
Kris
10
@RonLugge Você está certo. Com mais retrospectiva e experiência, discordo do meu comentário de três anos de idade. :)
Samy Dindane 20/03

Respostas:

196

Use #instance_eval

@controller = AccountController.new
@controller.instance_eval{ current_account }   # invoke the private method
@controller.instance_eval{ @current_account }.should eql ... # check the value of the instance variable
monóculo
fonte
94
Se preferir, você também pode dizer: @ controller.send (: current_account).
Confusão
13
Ruby permite que você chame métodos particulares com send, mas isso não significa necessariamente que você deveria. O teste de métodos particulares é feito através do teste da interface pública para esses métodos. Essa abordagem funcionará, mas não é ideal. Seria melhor se o método estivesse em um módulo que foi incluído no controlador. Em seguida, ele também poderia ser testado independentemente do controlador.
Brian Hogan
9
Mesmo assim, essa resposta tecnicamente responde à pergunta, estou com uma votação baixa, porque viola as práticas recomendadas em testes. Métodos particulares não devem ser testados e, apenas porque o Ruby oferece a capacidade de burlar a visibilidade do método, isso não significa que você deve abusar dele.
Srdjan Pejic
24
Srdjan Pejic, você poderia explicar por que métodos privados não devem ser testados?
John Bachir
35
Acho que você rebateu respostas erradas ou que não respondem à pergunta. Esta resposta está correta e não deve ser rebaixada. Se você não concordar com a prática de testar métodos privados, coloque isso nos comentários, pois são boas informações (como muitos fizeram) e as pessoas poderão votar novamente nesse comentário, o que ainda mostra o seu ponto, sem diminuir a votação desnecessariamente de uma resposta prefectly válida.
traday
37

Eu uso o método send. Por exemplo:

event.send(:private_method).should == 2

Porque "send" pode chamar métodos privados

Graffzon
fonte
Como você testaria variáveis ​​de instância no método privado usando .send?
the12
23

Onde o método current_account está sendo usado? Que finalidade serve?

Geralmente, você não testa métodos particulares, mas testa os métodos que chamam o particular.

Ryan Bigg
fonte
5
Idealmente, deve-se testar todos os métodos. Eu tenho usado tanto subject.send e subject.instance_eval com muito sucesso em rspec
David W. Keith
7
@Pullets Não concordo, você deve testar os métodos públicos da API que chamarão os privados, como minha resposta original diz. Você deve testar a API que fornece, não os métodos particulares que apenas você pode ver.
Ryan Bigg
5
Eu concordo com @Ryan Bigg. Você não testa métodos particulares. Isso remove sua capacidade de refatorar ou alterar a implementação do referido método, mesmo que essa alteração não afete as partes públicas do seu código. Leia as práticas recomendadas ao escrever testes automatizados.
Srdjan Pejic
4
Hmm, talvez eu esteja perdendo alguma coisa. As aulas que escrevo tinham muito mais métodos particulares do que os públicos. Testar apenas por meio da API pública resultaria em uma lista de centenas de testes que não refletem o código que estão testando.
David W. Keith
9
De acordo com meu entendimento, se você deseja obter uma granularidade real nos métodos particulares de teste de unidade, também deve ser testado. Se você deseja refatorar o teste de unidade de código, também deve ser refatorado de acordo. Isso garante que seu novo código também funcione conforme o esperado.
Indika K
7

Você não deveria testar seus métodos particulares diretamente, eles podem e devem ser testados indiretamente, exercitando o código de métodos públicos.

Isso permite que você altere as partes internas do seu código no futuro sem precisar alterar seus testes.

Lorem Ipsum Dolor
fonte
4

Você pode tornar seus métodos privados ou protegidos públicos:

MyClass.send(:public, *MyClass.protected_instance_methods) 
MyClass.send(:public, *MyClass.private_instance_methods)

Basta colocar esse código na sua classe de teste, substituindo o nome da sua classe. Inclua o espaço para nome, se aplicável.

zishe
fonte
3
require 'spec_helper'

describe AdminsController do 
  it "-current_account should return correct value" do
    class AccountController
      def test_current_account
        current_account           
      end
    end

    account_constroller = AccountController.new
    account_controller.test_current_account.should be_correct             

   end
end
speedingdeer
fonte
1

Os métodos particulares de teste de unidade parecem muito fora de contexto com o comportamento do aplicativo.

Você está escrevendo seu código de chamada primeiro? Este código não é chamado no seu exemplo.

O comportamento é: você deseja que um objeto seja carregado de outro objeto.

context "When I am logged in"
  let(:user) { create(:user) }
  before { login_as user }

  context "with an account"
    let(:account) { create(:account) }
    before { user.update_attribute :account_id, account.id }

    context "viewing the list of accounts" do
      before { get :index }

      it "should load the current users account" do
        assigns(:current_account).should == account
      end
    end
  end
end

Por que você deseja gravar o teste fora do contexto a partir do comportamento que você deveria estar tentando descrever?

Esse código é usado em muitos lugares? Precisa de uma abordagem mais genérica?

https://www.relishapp.com/rspec/rspec-rails/v/2-8/docs/controller-specs/anonymous-controller

Brent Greeff
fonte
1

Use a gema rspec-context-private para tornar temporariamente públicos métodos públicos em um contexto.

gem 'rspec-context-private'

Ele funciona adicionando um contexto compartilhado ao seu projeto.

RSpec.shared_context 'private', private: true do

  before :all do
    described_class.class_eval do
      @original_private_instance_methods = private_instance_methods
      public *@original_private_instance_methods
    end
  end

  after :all do
    described_class.class_eval do
      private *@original_private_instance_methods
    end
  end

end

Então, se você passar :privatecomo metadados para um describebloco, os métodos privados serão públicos nesse contexto.

describe AccountController, :private do
  it 'can test private methods' do
    expect{subject.current_account}.not_to raise_error
  end
end
pouco conhecido
fonte
0

Se você precisar testar uma função privada, crie um método público que chame a função privada.

liamfriel
fonte
3
Suponho que você queira dizer que isso deve ser feito no seu código de teste de unidade. Isso é essencialmente o que .instance_eval e .send fazem em uma única linha de código. (E quem quer escrever testes mais longos, quando um menor tem o mesmo efeito?)
David W. Keith
3
suspiro, é um controlador de trilhos. O método deve ser privado. Obrigado por ler a pergunta real.
Michael Johnston
Você sempre pode abstrair o método privado para um método público em um objeto de serviço e referenciá-lo dessa maneira. Dessa forma, você pode testar apenas métodos públicos e ainda assim manter seu código SECO.
Jason
0

Eu sei que isso é meio hacky, mas funciona se você quiser os métodos testáveis ​​pelo rspec, mas não visíveis no prod.

class Foo
  def public_method
    #some stuff
  end

  eval('private') unless Rails.env == 'test'

  def testable_private_method
    # You can test me if you set RAILS_ENV=test
  end 
end

Agora, quando você pode executar, está assim:

RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 
onetwopunch
fonte