Rspec, Rails: як перевірити приватні методи контролерів?


125

У мене контролер:

class AccountController < ApplicationController
  def index
  end

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

Як перевірити приватний метод current_account допомогою rspec?

PS Я використовую Rspec2 та Ruby on Rails 3


8
Це не відповідає на ваше запитання, але приватні методи не слід перевіряти. Ваші тести повинні дбати лише про реальну річ - ваш публічний API. Якщо ваші публічні методи працюють, то працюють і приватні, які вони називають.
Samy Dindane

77
Я не погоджуюсь. Цінність тестування будь-якої досить складної функції у вашому коді.
whitehat101

11
Я також не згоден. Якщо ваш загальнодоступний API працює, ви можете припускати, що ваші приватні методи працюють, як очікувалося. Але ваші характеристики можуть проходити за збігом обставин.
Рім’ян

4
Було б краще витягнути приватний метод до нового класу, який можна перевірити, якщо приватний метод потребує тестування.
Кріс

10
@RonLugge Ти маєш рацію. Маючи більше уваги та досвіду, я не згоден із своїм трирічним коментарем. :)
Samy Dindane

Відповіді:


196

Використовуйте #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
Якщо ви хочете, ви також можете сказати: @ controller.send (: current_account).
Плутанина

13
Ruby дозволяє викликати приватні методи за допомогою send, але це не обов'язково означає, що слід. Тестування приватних методів проводиться шляхом тестування публічного інтерфейсу до цих методів. Такий підхід спрацює, але це не ідеально. Було б краще, якби метод був у модулі, що входив до контролера. Тоді його можна було б протестувати і незалежно від контролера.
Брайан Хоган

9
Незважаючи на те, що ця відповідь технічно відповідає на питання, я спростовую її, оскільки це порушує найкращі практики тестування. Приватні методи не повинні перевірятися, і лише тому, що Ruby дає вам можливість обійти видимість методу, це не означає, що ви повинні зловживати ним.
Srdjan Pejic

24
Srdjan Pejic Ви могли б детальніше пояснити, чому приватні методи не слід перевіряти?
Джон Бачір

35
Я думаю, що ти спростовуєш відповіді, які є невірними, або не відповідаєш на питання. Ця відповідь правильна, і її не слід оскаржувати. Якщо ви не погоджуєтесь з практикою тестування приватних методів, додайте це до коментарів, оскільки це є хорошою інформацією (як багато хто зробив), і тоді люди можуть підтримати цей коментар, який все одно показує вашу думку, без зайвого зволікання на чітко вагому відповідь.
торг

37

Я використовую метод відправки. Наприклад:

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

Тому що "відправити" можна викликати приватні методи


Як би ви протестували змінні екземплярів у приватному методі, використовуючи .send?
the12

23

Де використовується метод current_account? Якій цілі він служить?

Як правило, ви не тестуєте приватні методи, а скоріше випробовуєте методи, що викликають приватні.


5
В ідеалі слід перевірити кожен метод. Я користувався як subject.send та subject.instance_eval з великим успіхом у rspec
David W. Keith

7
@Pullets Я не погоджуюся, ви повинні тестувати публічні методи API, які будуть викликати приватні, як мовиться в моїй оригінальній відповіді. Ви повинні тестувати наданий API, а не приватні методи, які ви можете бачити.
Райан Бігг

5
Я згоден з @Ryan Bigg. Ви не перевіряєте приватні методи. Це вилучає вашу здатність переробляти або змінювати реалізацію зазначеного методу, навіть якщо ця зміна не впливає на загальнодоступні частини вашого коду. Будь ласка, читайте про кращі практики при написанні автоматизованих тестів.
Srdjan Pejic

4
Гм, може, я щось пропускаю. Класи, які я пишу, мали набагато більше приватних методів, ніж публічні. Тестування лише через загальнодоступний API призведе до переліку сотень тестів, які не відображають код, який вони тестують.
David W. Keith

9
На моє розуміння, якщо ви хочете досягти реальної деталізації в одиничному тестуванні, також слід перевірити приватні методи. Якщо ви хочете тестувати блок коду рефактора, тест також повинен бути відновлений відповідно. Це гарантує, що ваш новий код також працює як очікувалося.
Indika K

7

Ви не повинні перевіряти свої приватні методи безпосередньо, вони можуть і повинні бути перевірені опосередковано, використовуючи код з публічних методів.

Це дозволяє змінити внутрішній код вашого дороги, не змінюючи тести.


4

Ви можете зробити приватні або захищені методи загальнодоступними:

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

Просто розмістіть цей код у своєму тестовому класі, замінивши його ім'я. Додайте простір імен, якщо це можливо.


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

Приватні методи перевірки приватних методів здаються занадто поза контекстом з поведінкою програми.

Ви пишете свій телефонний код спочатку? Цей код не називається у вашому прикладі.

Поведінка така: ви хочете, щоб об'єкт завантажився з іншого об'єкта.

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

Чому ти хочеш написати тест поза контекстом з поведінки, яку ти повинен намагатися описати?

Чи використовується цей код у багатьох місцях? Вам потрібен більш загальний підхід?

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


1

Використовуйте дорогоцінний камінь rspec-context-private, щоб тимчасово оприлюднити приватні методи у контексті.

gem 'rspec-context-private'

Це працює, додаючи спільний контекст до вашого проекту.

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

Тоді, якщо ви передасте :privateметадані до describeблоку, приватні методи будуть відкритими в цьому контексті.

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

0

Якщо вам потрібно перевірити приватну функцію, створіть публічний метод, який викликає приватний.


3
Я припускаю, що ви маєте на увазі, що це слід зробити у вашому коді тестування одиниці. Це по суті те, що .instance_eval і .send роблять в одному рядку коду. (А хто хоче писати довші тести, коли коротший має той самий ефект?)
Девід В. Кіт

3
зітхайте, це контролер рейки. Метод повинен бути приватним. Дякуємо, що прочитали власне запитання.
Майкл Джонстон

Ви завжди можете абстрагувати приватний метод до публічного методу в об’єкті обслуговування та посилатися на нього таким чином. Таким чином, ви можете мати тестові лише загальнодоступні методи, і все ж зберігати свій код DRY.
Джейсон

0

Я знаю, що це начебто хакі, але це працює, якщо ви хочете, щоб методи перевірялися rspec, але не були видимі в 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

Тепер, коли ви можете запустити, у вас є такі характеристики:

RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.