Rspec, Rails: come testare metodi privati ​​di controller?


125

Ho un controller:

class AccountController < ApplicationController
  def index
  end

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

Come testare il metodo privato current_accountcon rspec?

PS Uso Rspec2 e Ruby on Rails 3


8
Questo non risponde alla tua domanda, ma i metodi privati ​​non dovrebbero essere testati. I tuoi test dovrebbero preoccuparsi solo della cosa reale : la tua API pubblica. Se i tuoi metodi pubblici funzionano, funzionano anche quelli privati ​​che chiamano.
Samy Dindane,

77
Non sono d'accordo. È utile testare qualsiasi funzione sufficientemente complessa nel codice.
Whitehat101,

11
Non sono d'accordo anche io. Se l'API pubblica funziona, puoi solo supporre che i tuoi metodi privati ​​funzionino come previsto. Ma le tue specifiche potrebbero passare per coincidenza.
Rimian,

4
Sarebbe meglio estrarre il metodo privato in una nuova classe che è testabile, se il metodo privato deve essere testato.
Kris,

10
@RonLugge Hai ragione. Con il senno di poi e l'esperienza, non sono d'accordo con il mio commento di tre anni. :)
Samy Dindane,

Risposte:


196

Usa #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

94
Se preferisci, puoi anche dire: @ controller.send (: current_account).
Confusione

13
Ruby ti consente di chiamare metodi privati ​​con send, ma ciò non significa necessariamente che dovresti. Il test dei metodi privati ​​viene eseguito testando l'interfaccia pubblica con tali metodi. Questo approccio funzionerà, ma non è l'ideale. Sarebbe meglio se il metodo fosse in un modulo che è stato incluso nel controller. Quindi potrebbe essere testato indipendentemente dal controller.
Brian Hogan,

9
Anche se questa risposta risponde tecnicamente alla domanda, la sto ridimensionando perché viola le migliori pratiche nei test. I metodi privati ​​non dovrebbero essere testati e solo perché Ruby ti dà la possibilità di eludere la visibilità del metodo, ciò non significa che dovresti abusarne.
Srdjan Pejic,

24
Srdjan Pejic potresti approfondire il motivo per cui i metodi privati ​​non dovrebbero essere testati?
John Bachir,

35
Penso che tu abbia votato in negativo risposte errate o che non rispondono alla domanda. Questa risposta è corretta e non dovrebbe essere sottoposta a downgrade. Se non sei d'accordo con la pratica di testare metodi privati, inseriscilo nei commenti in quanto è una buona informazione (come molti hanno fatto) e quindi le persone possono votare quel commento, che mostra ancora il tuo punto senza inutilmente ridimensionare una risposta perfettamente valida.
trad

37

Uso il metodo di invio. Per esempio:

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

Perché "invia" può chiamare metodi privati


Come testereste le variabili di istanza nel metodo privato usando .send?
the12

23

Dove viene utilizzato il metodo current_account? A quale scopo serve?

Generalmente, non testate metodi privati ​​ma piuttosto testate i metodi che chiamano quello privato.


5
Idealmente si dovrebbe testare ogni metodo. Ho usato sia subject.send che subject.instance_eval con molto successo in rspec
David W. Keith

7
@Pullet Non sono d'accordo, dovresti testare i metodi pubblici dell'API che chiameranno quelli privati, come dice la mia risposta originale. Dovresti testare l'API che fornisci, non solo i metodi privati ​​che puoi vedere.
Ryan Bigg,

5
Sono d'accordo con @Ryan Bigg. Non testare metodi privati. Questo rimuove la tua capacità di refactoring o modificare l'implementazione di detto metodo, anche se tale modifica non influisce sulle parti pubbliche del tuo codice. Si prega di leggere le migliori pratiche quando si scrivono test automatici.
Srdjan Pejic,

4
Forse mi manca qualcosa. Le classi che scrivo avevano molti più metodi privati ​​di quelli pubblici. Testare solo tramite l'API pubblica comporterebbe un elenco di centinaia di test che non rispecchiano il codice che stanno testando.
David W. Keith,

9
Secondo la mia comprensione, se si desidera ottenere una granularità reale nei test unitari, è necessario testare anche metodi privati. Se si desidera effettuare il refactoring, anche il test unitario del codice deve essere sottoposto a refactoring di conseguenza. Questo assicura che il tuo nuovo codice funzioni anche come previsto.
Indika K,

7

Si dovrebbe non prova i tuoi metodi privati direttamente, che può e deve essere testato indirettamente esercitando il codice metodi pubblici.

Ciò ti consente di modificare gli interni del codice lungo la strada senza dover cambiare i test.


4

Potresti renderti metodi privati ​​o protetti come pubblici:

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

Basta inserire questo codice nella classe di test sostituendo il nome della classe. Includi lo spazio dei nomi, se applicabile.


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

1

L'unità di test dei metodi privati ​​sembra troppo fuori contesto con il comportamento dell'applicazione.

Scrivi prima il tuo codice chiamante? Questo codice non è chiamato nel tuo esempio.

Il comportamento è: si desidera caricare un oggetto da un altro oggetto.

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

Perché vuoi scrivere il test fuori contesto dal comportamento che dovresti provare a descrivere?

Questo codice viene utilizzato in molti luoghi? Hai bisogno di un approccio più generico?

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


1

Usa la gemma rspec-context-private per rendere temporaneamente pubblici i metodi privati ​​in un contesto.

gem 'rspec-context-private'

Funziona aggiungendo un contesto condiviso al tuo progetto.

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

Quindi, se passi :privatecome metadati a un describeblocco, i metodi privati ​​saranno pubblici all'interno di quel contesto.

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

0

Se è necessario testare una funzione privata, creare un metodo pubblico che richiami quello privato.


3
Presumo tu intenda che ciò dovrebbe essere fatto nel tuo codice di test unitario. Questo è essenzialmente ciò che fanno .instance_eval e .send in una singola riga di codice. (E chi vuole scrivere test più lunghi quando uno più corto ha lo stesso effetto?)
David W. Keith

3
sospiro, è un controller di binari. Il metodo deve essere privato. Grazie per aver letto la vera domanda.
Michael Johnston,

È sempre possibile astrarre il metodo privato su un metodo pubblico in un oggetto servizio e fare riferimento in quel modo. In questo modo puoi avere solo metodi pubblici di prova e mantenere comunque il tuo codice DRY.
Jason,

0

So che questo è un po 'confuso, ma funziona se vuoi che i metodi siano testabili da rspec ma non siano visibili in 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

Ora quando puoi eseguire le tue specifiche in questo modo:

RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.