Rspec, Rails: jak przetestować prywatne metody kontrolerów?

125

Mam kontroler:

class AccountController < ApplicationController
  def index
  end

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

Jak przetestować metodę prywatną current_accountz rspec?

PS Używam Rspec2 i Ruby on Rails 3

petRUShka
źródło
8
To nie odpowiada na twoje pytanie, ale prywatne metody nie powinny być testowane. Twoje testy powinny zajmować się tylko prawdziwą rzeczą - twoim publicznym API. Jeśli twoje metody publiczne działają, to działają również te prywatne, które nazywają.
Samy Dindane
77
Nie zgadzam się. Testowanie dowolnej wystarczająco złożonej funkcji w kodzie ma wartość.
whitehat101
11
Ja też się nie zgadzam. Jeśli Twój publiczny interfejs API działa, możesz tylko założyć, że Twoje metody prywatne działają zgodnie z oczekiwaniami. Ale twoje specyfikacje mogą być przypadkowe.
Rimian
4
Byłoby lepiej wyodrębnić metodę prywatną do nowej klasy, która jest testowalna, jeśli metoda prywatna wymaga testowania.
Kris
10
@RonLugge Masz rację. Z perspektywy czasu i doświadczenia nie zgadzam się z moim komentarzem sprzed trzech lat. :)
Samy Dindane

Odpowiedzi:

196

Użyj #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
monokl
źródło
94
Jeśli wolisz, możesz też powiedzieć: @ controller.send (: current_account).
Zamieszanie
13
Ruby umożliwia wywoływanie prywatnych metod z send, ale to niekoniecznie oznacza, że ​​powinieneś. Testowanie metod prywatnych odbywa się poprzez testowanie publicznego interfejsu tych metod. To podejście zadziała, ale nie jest idealne. Byłoby lepiej, gdyby metoda była w module dołączonym do kontrolera. Następnie można go również przetestować niezależnie od kontrolera.
Brian Hogan,
9
Mimo że ta odpowiedź technicznie odpowiada na pytanie, odrzucam ją, ponieważ narusza najlepsze praktyki testowania. Prywatne metody nie powinny być testowane, a tylko dlatego, że Ruby daje możliwość obejścia widoczności metody, nie oznacza, że ​​powinieneś jej nadużywać.
Srdjan Pejic
24
Srdjan Pejic, czy mógłbyś wyjaśnić, dlaczego nie należy testować metod prywatnych?
John Bachir,
35
Myślę, że przeciwstawiasz się odpowiedziom, które są błędne lub nie odpowiadają na pytanie. Ta odpowiedź jest poprawna i nie należy jej odrzucać. Jeśli nie zgadzasz się z praktyką testowania prywatnych metod, umieść to w komentarzach, ponieważ jest to dobra informacja (jak wielu to zrobiło), a wtedy ludzie będą mogli głosować za tym komentarzem, który nadal pokazuje Twój punkt widzenia bez niepotrzebnego odrzucania idealnie trafnej odpowiedzi.
traday
37

Używam metody wysyłania. Na przykład:

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

Ponieważ „wyślij” może wywoływać metody prywatne

graffzon
źródło
Jak przetestowałbyś zmienne instancji w ramach metody prywatnej przy użyciu .send?
12
23

Gdzie jest używana metoda current_account? Do czego to służy?

Ogólnie rzecz biorąc, nie testujesz metod prywatnych, ale raczej testujesz metody, które wywołują metodę prywatną.

Ryan Bigg
źródło
5
Najlepiej byłoby przetestować każdą metodę. Użyłem zarówno subject.send, jak i subject.instance_eval z dużym powodzeniem w rspec
David W. Keith
7
@Pullets Nie zgadzam się, powinieneś przetestować publiczne metody API, które będą wywoływały prywatne, jak mówi moja pierwotna odpowiedź. Powinieneś testować udostępniony interfejs API, a nie metody prywatne, które tylko Ty widzisz.
Ryan Bigg,
5
Zgadzam się z @Ryan Bigg. Nie testujesz metod prywatnych. Eliminuje to możliwość refaktoryzacji lub zmiany implementacji wspomnianej metody, nawet jeśli ta zmiana nie wpływa na publiczne części kodu. Zapoznaj się z najlepszymi praktykami podczas pisania testów automatycznych.
Srdjan Pejic
4
Hmm, może czegoś mi brakuje. Zajęcia, które piszę, miały znacznie więcej metod prywatnych niż publicznych. Testowanie tylko za pośrednictwem publicznego interfejsu API spowodowałoby powstanie listy setek testów, które nie odzwierciedlają testowanego kodu.
David W. Keith,
9
Zgodnie z moim zrozumieniem, jeśli chcesz osiągnąć prawdziwą szczegółowość w testach jednostkowych, należy również przetestować prywatne metody. Jeśli chcesz refaktoryzować kod jednostkowy, test również musi być odpowiednio refaktoryzowany. Gwarantuje to, że Twój nowy kod również działa zgodnie z oczekiwaniami.
Indika K
7

Należy nie testować metod prywatnych bezpośrednio, mogą i powinny być testowane pośrednio poprzez wykonywanie kodu z metod publicznych.

Pozwala to na zmianę elementów wewnętrznych kodu w przyszłości bez konieczności zmiany testów.

Lorem Ipsum Dolor
źródło
4

Możesz uczynić swoje prywatne lub chronione metody jako publiczne:

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

Po prostu umieść ten kod w swojej klasie testowej, zastępując nazwę swojej klasy. W razie potrzeby uwzględnij przestrzeń nazw.

zishe
źródło
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
źródło
1

Prywatne metody testów jednostkowych wydają się zbyt wyrwane z kontekstu zachowania aplikacji.

Czy najpierw piszesz kod telefoniczny? Ten kod nie jest wywoływany w Twoim przykładzie.

Zachowanie jest następujące: chcesz, aby obiekt został załadowany z innego obiektu.

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

Dlaczego chcesz napisać test wyrwany z kontekstu zachowania, które powinieneś próbować opisać?

Czy ten kod jest używany w wielu miejscach? Potrzebujesz bardziej ogólnego podejścia?

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

Brent Greeff
źródło
1

Użyj klejnotu rspec-context-private, aby tymczasowo upublicznić metody prywatne w kontekście.

gem 'rspec-context-private'

Działa poprzez dodanie współdzielonego kontekstu do twojego projektu.

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

Następnie, jeśli przekażesz :privatejako metadane do describebloku, metody prywatne będą publiczne w tym kontekście.

describe AccountController, :private do
  it 'can test private methods' do
    expect{subject.current_account}.not_to raise_error
  end
end
ledwo znany
źródło
0

Jeśli chcesz przetestować funkcję prywatną, utwórz metodę publiczną, która wywoła funkcję prywatną.

liamfriel
źródło
3
Zakładam, że masz na myśli, że należy to zrobić w kodzie testu jednostkowego. To właśnie robią .instance_eval i .send w pojedynczej linii kodu. (A kto chce pisać dłuższe testy, gdy krótszy daje taki sam efekt?)
David W. Keith
3
westchnienie, to kontroler szyn. Metoda musi być prywatna. Dziękuję za przeczytanie właściwego pytania.
Michael Johnston,
Zawsze można wyodrębnić metodę prywatną do metody publicznej w obiekcie usługi i odwołać się do niej w ten sposób. W ten sposób możesz testować tylko metody publiczne, a mimo to zachować kod DRY.
Jason,
0

Wiem, że to trochę hacky, ale działa, jeśli chcesz, aby metody były testowane przez rspec, ale nie były widoczne w 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

Teraz, kiedy możesz biegać, masz taką specyfikację:

RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 
onetwopunch
źródło