Rspec, Rails: comment tester les méthodes privées des contrôleurs?

125

J'ai le contrôleur:

class AccountController < ApplicationController
  def index
  end

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

Comment tester une méthode privée current_accountavec rspec?

PS J'utilise Rspec2 et Ruby on Rails 3

petRUShka
la source
8
Cela ne répond pas à votre question, mais les méthodes privées ne sont pas censées être testées. Vos tests ne devraient porter que sur la réalité : votre API publique. Si vos méthodes publiques fonctionnent, les méthodes privées qu'elles appellent fonctionnent également.
Samy Dindane
77
Je ne suis pas d'accord. Il est utile de tester toute fonctionnalité suffisamment complexe de votre code.
whitehat101
11
Je suis également en désaccord. Si votre API publique fonctionne, vous pouvez uniquement supposer que vos méthodes privées fonctionnent comme prévu. Mais vos spécifications pourraient passer par hasard.
Rimian
4
Il serait préférable d'extraire la méthode privée dans une nouvelle classe testable, si la méthode privée doit être testée.
Kris
10
@RonLugge Vous avez raison. Avec plus de recul et d'expérience, je ne suis pas d'accord avec mon commentaire de trois ans. :)
Samy Dindane

Réponses:

196

Utilisez #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
monocle
la source
94
Si vous préférez, vous pouvez également dire: @ controller.send (: current_account).
Confusion
13
Ruby vous permet d'appeler des méthodes privées avec send, mais cela ne signifie pas nécessairement que vous devriez le faire. Le test des méthodes privées se fait en testant l'interface publique avec ces méthodes. Cette approche fonctionnera, mais ce n'est pas idéal. Ce serait mieux si la méthode était dans un module inclus dans le contrôleur. Ensuite, il pourrait également être testé indépendamment du contrôleur.
Brian Hogan
9
Même si cette réponse répond techniquement à la question, je la vote contre, car elle enfreint les meilleures pratiques en matière de test. Les méthodes privées ne doivent pas être testées, et ce n'est pas parce que Ruby vous donne la possibilité de contourner la visibilité des méthodes que vous devez en abuser.
Srdjan Pejic
24
Srdjan Pejic pourriez-vous expliquer pourquoi les méthodes privées ne devraient pas être testées?
John Bachir
35
Je pense que vous évaluez les réponses qui sont fausses ou que vous ne répondez pas à la question. Cette réponse est correcte et ne doit pas être rejetée. Si vous n'êtes pas d'accord avec la pratique de tester des méthodes privées, mettez cela dans les commentaires car il s'agit de bonnes informations (comme beaucoup l'ont fait) et les gens peuvent voter pour ce commentaire, ce qui montre toujours votre point de vue sans voter inutilement une réponse parfaitement valide.
traday
37

J'utilise la méthode d'envoi. Par exemple:

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

Parce que "envoyer" peut appeler des méthodes privées

graffzon
la source
Comment testeriez-vous les variables d'instance dans la méthode privée en utilisant .send?
le12
23

Où la méthode current_account est-elle utilisée? A quoi cela sert-il?

Généralement, vous ne testez pas les méthodes privées mais testez plutôt les méthodes qui appellent la méthode privée.

Ryan Bigg
la source
5
Idéalement, on devrait tester chaque méthode. J'ai utilisé à la fois subject.send et subject.instance_eval avec beaucoup de succès dans rspec
David W. Keith
7
@Pullets Je ne suis pas d'accord, vous devriez tester les méthodes publiques de l'API qui appelleront les méthodes privées, comme le dit ma réponse originale. Vous devriez tester l'API que vous fournissez, pas les méthodes privées que vous seul pouvez voir.
Ryan Bigg
5
Je suis d'accord avec @Ryan Bigg. Vous ne testez pas les méthodes privées. Cela vous empêche de refactoriser ou de modifier l'implémentation de ladite méthode, même si ce changement n'affecte pas les parties publiques de votre code. Veuillez lire les meilleures pratiques lors de la rédaction de tests automatisés.
Srdjan Pejic
4
Hmm, il me manque peut-être quelque chose. Les classes que j'écris avaient beaucoup plus de méthodes privées que de méthodes publiques. Tester uniquement via l'API publique aboutirait à une liste de centaines de tests qui ne reflètent pas le code qu'ils testent.
David W. Keith
9
Selon ma compréhension, si vous souhaitez obtenir une granularité réelle dans les tests unitaires, les méthodes privées doivent également être testées. Si vous souhaitez refactoriser le code, le test unitaire doit également être refactorisé en conséquence. Cela garantit que votre nouveau code fonctionne également comme prévu.
Indika K
7

Vous ne devez pas tester vos méthodes privées directement, elles peuvent et doivent être testées indirectement en utilisant le code à partir de méthodes publiques.

Cela vous permet de modifier ultérieurement les éléments internes de votre code sans avoir à modifier vos tests.

Lorem Ipsum Dolor
la source
4

Vous pouvez rendre vos méthodes privées ou protégées publiques:

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

Placez simplement ce code dans votre classe de test en remplaçant votre nom de classe. Incluez l'espace de noms le cas échéant.

zishe
la source
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
excès de vitesse
la source
1

Les tests unitaires de méthodes privées semblent trop hors contexte avec le comportement de l'application.

Écrivez-vous d'abord votre code d'appel? Ce code n'est pas appelé dans votre exemple.

Le comportement est le suivant: vous voulez un objet chargé à partir d'un autre objet.

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

Pourquoi voulez-vous écrire le test hors de son contexte à partir du comportement que vous devriez essayer de décrire?

Ce code est-il utilisé dans de nombreux endroits? Besoin d'une approche plus générique?

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

Brent Greeff
la source
1

Utilisez le gem rspec-context-private pour rendre temporairement les méthodes privées publiques dans un contexte.

gem 'rspec-context-private'

Cela fonctionne en ajoutant un contexte partagé à votre projet.

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

Ensuite, si vous passez en :privatetant que métadonnées à un describebloc, les méthodes privées seront publiques dans ce contexte.

describe AccountController, :private do
  it 'can test private methods' do
    expect{subject.current_account}.not_to raise_error
  end
end
à peine connu
la source
0

Si vous avez besoin de tester une fonction privée, créez une méthode publique qui appelle la méthode privée.

Liamfriel
la source
3
Je suppose que vous voulez dire que cela devrait être fait dans votre code de test unitaire. C'est essentiellement ce que font .instance_eval et .send dans une seule ligne de code. (Et qui veut écrire des tests plus longs alors qu'un plus court a le même effet?)
David W. Keith
3
soupir, c'est un contrôleur de rails. La méthode doit être privée. Merci d'avoir lu la question.
Michael Johnston
Vous pouvez toujours faire abstraction de la méthode privée à une méthode publique dans un objet de service et la référencer de cette façon. De cette façon, vous pouvez tester uniquement les méthodes publiques, tout en conservant votre code DRY.
Jason
0

Je sais que c'est un peu hacky, mais cela fonctionne si vous voulez que les méthodes soient testables par rspec mais pas visibles dans 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

Maintenant, quand vous pouvez exécuter vos spécifications comme ceci:

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