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.