Rspec, Rails: Wie teste ich private Methoden von Controllern?


125

Ich habe Controller:

class AccountController < ApplicationController
  def index
  end

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

Wie current_accountteste ich eine private Methode mit rspec?

PS Ich benutze Rspec2 und Ruby on Rails 3


8
Dies beantwortet Ihre Frage nicht, aber private Methoden sollten nicht getestet werden. Ihre Tests sollten sich nur um die Realität kümmern - Ihre öffentliche API. Wenn Ihre öffentlichen Methoden funktionieren, funktionieren auch die privaten Methoden, die sie aufrufen.
Samy Dindane

77
Ich bin nicht einverstanden. Es ist sinnvoll, ausreichend komplexe Funktionen in Ihrem Code zu testen.
Whitehat101

11
Ich bin auch anderer Meinung. Wenn Ihre öffentliche API funktioniert, können Sie nur davon ausgehen, dass Ihre privaten Methoden wie erwartet funktionieren. Aber Ihre Angaben könnten zufällig passieren.
Rimian

4
Es wäre besser, die private Methode in eine neue Klasse zu extrahieren, die testbar ist, wenn die private Methode getestet werden muss.
Kris

10
@ RonLugge Du hast recht. Im Nachhinein und mit meiner Erfahrung bin ich mit meinem drei Jahre alten Kommentar nicht einverstanden. :)
Samy Dindane

Antworten:


196

Verwenden Sie #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
Wenn Sie möchten, können Sie auch sagen: @ controller.send (: current_account).
Verwirrung

13
Mit Ruby können Sie private Methoden mit send aufrufen, aber das bedeutet nicht unbedingt, dass Sie dies tun sollten. Das Testen privater Methoden erfolgt durch Testen der öffentlichen Schnittstelle zu diesen Methoden. Dieser Ansatz wird funktionieren, ist aber nicht ideal. Es wäre besser, wenn sich die Methode in einem Modul befindet, das in der Steuerung enthalten ist. Dann könnte es auch unabhängig von der Steuerung getestet werden.
Brian Hogan

9
Obwohl diese Antwort die Frage technisch beantwortet, stimme ich sie ab, da sie gegen Best Practices beim Testen verstößt. Private Methoden sollten nicht getestet werden. Nur weil Ruby Ihnen die Möglichkeit gibt, die Sichtbarkeit von Methoden zu umgehen, bedeutet dies nicht, dass Sie sie missbrauchen sollten.
Srdjan Pejic

24
Srdjan Pejic, können Sie erläutern, warum private Methoden nicht getestet werden sollten?
John Bachir

35
Ich denke, Sie stimmen falsche Antworten ab oder beantworten die Frage nicht. Diese Antwort ist richtig und sollte nicht herabgestuft werden. Wenn Sie mit dem Testen privater Methoden nicht einverstanden sind, geben Sie dies in die Kommentare ein, da es sich um gute Informationen handelt (wie viele), und dann können die Leute diesen Kommentar positiv bewerten, was immer noch Ihren Standpunkt zeigt, ohne unnötig eine perfekt gültige Antwort herabzustimmen.
Tag

37

Ich benutze die Sendemethode. Z.B:

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

Weil "send" private Methoden aufrufen kann


Wie würden Sie Instanzvariablen innerhalb der privaten Methode testen .send?
The12

23

Wo wird die current_account-Methode verwendet? Welchen Zweck erfüllt es?

Im Allgemeinen testen Sie keine privaten Methoden, sondern die Methoden, die die private aufrufen.


5
Idealerweise sollte man jede Methode testen. Ich habe sowohl subject.send als auch subject.instance_eval mit viel Erfolg in rspec verwendet
David W. Keith

7
@Pullets Ich bin anderer Meinung, Sie sollten die öffentlichen Methoden der API testen, die die privaten aufrufen, wie meine ursprüngliche Antwort besagt. Sie sollten die von Ihnen bereitgestellte API testen, nicht die privaten Methoden, die nur Sie sehen können.
Ryan Bigg

5
Ich stimme @Ryan Bigg zu. Sie testen keine privaten Methoden. Dadurch können Sie die Implementierung dieser Methode nicht mehr umgestalten oder ändern, auch wenn diese Änderung keine Auswirkungen auf die öffentlichen Teile Ihres Codes hat. Bitte informieren Sie sich beim Schreiben automatisierter Tests über bewährte Methoden.
Srdjan Pejic

4
Hmm, vielleicht fehlt mir etwas. Die Klassen, die ich schreibe, hatten viel mehr private Methoden als öffentliche. Das Testen nur über die öffentliche API würde zu einer Liste von Hunderten von Tests führen, die den getesteten Code nicht widerspiegeln.
David W. Keith

9
Nach meinem Verständnis sollten auch private Methoden getestet werden, wenn Sie beim Testen von Einheiten eine echte Granularität erreichen möchten. Wenn Sie den Code umgestalten möchten, muss der Komponententest ebenfalls entsprechend umgestaltet werden. Dies stellt sicher, dass Ihr neuer Code auch wie erwartet funktioniert.
Indika K

7

Sie sollten nicht testen Sie Ihre private Methoden direkt, sie können und sollten durch die Ausübung des Codes von öffentlichen Methoden indirekt getestet werden.

Auf diese Weise können Sie die Interna Ihres Codes später ändern, ohne Ihre Tests ändern zu müssen.


4

Sie können Ihre privaten oder geschützten Methoden als öffentlich festlegen:

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

Fügen Sie diesen Code einfach in Ihre Testklasse ein und ersetzen Sie Ihren Klassennamen. Fügen Sie gegebenenfalls den Namespace hinzu.


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

Private Methoden zum Testen von Einheiten scheinen zu unzusammenhängend mit dem Verhalten der Anwendung zu sein.

Schreiben Sie zuerst Ihren Anrufcode? Dieser Code wird in Ihrem Beispiel nicht aufgerufen.

Das Verhalten ist: Sie möchten, dass ein Objekt von einem anderen Objekt geladen wird.

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

Warum möchten Sie den Test aus dem Kontext des Verhaltens heraus schreiben, das Sie beschreiben möchten?

Wird dieser Code an vielen Stellen verwendet? Benötigen Sie einen allgemeineren Ansatz?

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


1

Verwenden Sie das Juwel rspec-context-private , um private Methoden vorübergehend in einem Kontext zu veröffentlichen.

gem 'rspec-context-private'

Es funktioniert, indem Sie Ihrem Projekt einen gemeinsamen Kontext hinzufügen.

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

Wenn Sie dann :privateals Metadaten an einen describeBlock übergeben, sind die privaten Methoden in diesem Kontext öffentlich.

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

0

Wenn Sie eine private Funktion testen müssen, erstellen Sie eine öffentliche Methode, die die private aufruft.


3
Ich gehe davon aus, dass Sie meinen, dass dies in Ihrem Unit-Test-Code erfolgen sollte. Dies ist im Wesentlichen das, was .instance_eval und .send in einer einzigen Codezeile tun. (Und wer möchte längere Tests schreiben, wenn ein kürzerer den gleichen Effekt hat?)
David W. Keith

3
Seufz, es ist ein Rails Controller. Die Methode muss privat sein. Vielen Dank für das Lesen der eigentlichen Frage.
Michael Johnston

Sie können die private Methode jederzeit zu einer öffentlichen Methode in einem Serviceobjekt abstrahieren und auf diese Weise referenzieren. Auf diese Weise können Sie nur öffentliche Methoden testen lassen und trotzdem Ihren Code trocken halten.
Jason

0

Ich weiß, dass dies ein bisschen hackig ist, aber es funktioniert, wenn Sie möchten, dass die Methoden von rspec getestet werden können, aber in prod nicht sichtbar sind.

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

Wenn Sie jetzt laufen können, sind Sie wie folgt spezifiziert:

RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.