Rspec, Rails: ¿cómo probar métodos privados de controladores?

125

Tengo controlador:

class AccountController < ApplicationController
  def index
  end

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

¿Cómo probar el método privado current_accountcon rspec?

PD: uso Rspec2 y Ruby on Rails 3

petRUShka
fuente
8
Esto no responde a su pregunta, pero no se supone que los métodos privados se prueben. Sus pruebas solo deberían preocuparse por lo real : su API pública. Si sus métodos públicos funcionan, los privados a los que llaman también funcionan.
Samy Dindane
77
Estoy en desacuerdo. Hay valor en probar cualquier característica suficientemente compleja en su código.
whitehat101
11
No estoy de acuerdo también. Si su API pública funciona, solo puede suponer que sus métodos privados funcionan como se esperaba. Pero sus especificaciones podrían estar pasando por coincidencia.
Rimian
44
Sería mejor extraer el método privado en una nueva clase que sea comprobable, si el método privado necesita pruebas.
Kris
10
@RonLugge Tienes razón. Con más retrospectiva y experiencia, no estoy de acuerdo con mi comentario de tres años. :)
Samy Dindane

Respuestas:

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
fuente
94
Si lo prefiere, también puede decir: @ controller.send (: current_account).
Confusión
13
Ruby te permite llamar a métodos privados con send, pero eso no significa necesariamente que debas hacerlo. La prueba de los métodos privados se realiza mediante la prueba de la interfaz pública de esos métodos. Este enfoque funcionará, pero no es ideal. Sería mejor si el método estuviera en un módulo incluido en el controlador. Entonces podría probarse independientemente del controlador también.
Brian Hogan
9
Aunque esta respuesta técnicamente responde la pregunta, la estoy rechazando porque viola las mejores prácticas en las pruebas. Los métodos privados no deben probarse, y solo porque Ruby le brinde la capacidad de eludir la visibilidad del método, no significa que deba abusar de él.
Srdjan Pejic
24
Srdjan Pejic, ¿podría explicar por qué los métodos privados no deberían ser probados?
John Bachir
35
Creo que rechaza las respuestas incorrectas o no responde la pregunta. Esta respuesta es correcta y no debe ser rechazada. Si no está de acuerdo con la práctica de probar métodos privados, póngalo en los comentarios ya que es una buena información (como muchos lo hicieron) y luego la gente puede votar ese comentario, que aún muestra su punto sin necesidad de rechazar innecesariamente una respuesta válida válida.
traday
37

Yo uso el método de envío. P.ej:

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

Porque "enviar" puede llamar a métodos privados

graffzon
fuente
¿Cómo probaría las variables de instancia dentro del método privado usando .send?
12
23

¿Dónde se usa el método current_account? ¿Para qué sirve?

En general, no prueba los métodos privados, sino que prueba los métodos que llaman al privado.

Ryan Bigg
fuente
55
Idealmente, uno debería probar cada método. He usado tanto subject.send como subject.instance_eval con mucho éxito en rspec
David W. Keith
77
@Pullets No estoy de acuerdo, deberías probar los métodos públicos de la API que llamarán a los privados, como dice mi respuesta original. Debería probar la API que proporciona, no los métodos privados que solo usted puede ver.
Ryan Bigg
55
Estoy de acuerdo con @Ryan Bigg. No prueba métodos privados. Esto elimina su capacidad de refactorizar o cambiar la implementación de dicho método, incluso si ese cambio no afecta las partes públicas de su código. Lea sobre las mejores prácticas al escribir pruebas automatizadas.
Srdjan Pejic
44
Hmm, tal vez me estoy perdiendo algo. Las clases que escribo tenían muchos más métodos privados que públicos. Las pruebas solo a través de la API pública darían como resultado una lista de cientos de pruebas que no reflejan el código que están probando.
David W. Keith
9
Según tengo entendido, si desea lograr una granularidad real en las pruebas unitarias, también se deben probar los métodos privados. Si desea refactorizar la prueba de unidad de código también debe ser refactorizada en consecuencia. Esto asegura que su nuevo código también funcione como se esperaba.
Indika K
7

Usted debe no a prueba sus métodos privados directamente, se puede y debe ser probado indirectamente, mediante el ejercicio del código de métodos públicos.

Esto le permite cambiar las partes internas de su código en el futuro sin tener que cambiar sus pruebas.

Lorem Ipsum Dolor
fuente
4

Puede hacer que sus métodos privados o protegidos sean públicos:

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

Simplemente coloque este código en su clase de prueba sustituyendo su nombre de clase. Incluya el espacio de nombres si corresponde.

zishe
fuente
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
venado
fuente
1

Las pruebas unitarias de métodos privados parecen demasiado fuera de contexto con el comportamiento de la aplicación.

¿Estás escribiendo tu código de llamada primero? Este código no se llama en su ejemplo.

El comportamiento es: desea un objeto cargado desde otro 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 qué quieres escribir la prueba fuera de contexto a partir del comportamiento que deberías tratar de describir?

¿Este código se usa en muchos lugares? ¿Necesita un enfoque más genérico?

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

Brent Greeff
fuente
1

Use la gema rspec-context-private para hacer que los métodos privados sean públicos dentro de un contexto.

gem 'rspec-context-private'

Funciona agregando un contexto compartido a su proyecto.

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

Luego, si pasa :privatecomo metadatos a un describebloque, los métodos privados serán públicos dentro de ese contexto.

describe AccountController, :private do
  it 'can test private methods' do
    expect{subject.current_account}.not_to raise_error
  end
end
apenas conocido
fuente
0

Si necesita probar una función privada, cree un método público que invoque la privada.

liamfriel
fuente
3
Supongo que quiere decir que esto debe hacerse en el código de prueba de su unidad. Eso es esencialmente lo que hacen .instance_eval y .send en una sola línea de código. (¿Y quién quiere escribir exámenes más largos cuando uno más corto tiene el mismo efecto?)
David W. Keith
3
suspiro, es un controlador de rieles. El método tiene que ser privado. Gracias por leer la pregunta real.
Michael Johnston el
Siempre puede abstraer el método privado a un método público en un objeto de servicio y hacer referencia de esa manera. De esa manera, puede probar solo los métodos públicos y, aun así, mantener su código SECO.
Jason
0

Sé que esto es un poco hacky, pero funciona si quieres que los métodos sean verificables por rspec pero no visibles en 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

Ahora, cuando puede ejecutar su especificación así:

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