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 работает, вы можете только предполагать, что ваши частные методы работают должным образом. Но ваши характеристики могут совпадать случайно.
Rimian
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).
Confusion
13
Ruby позволяет вызывать частные методы с помощью send, но это не обязательно означает, что вы должны это делать. Тестирование частных методов осуществляется путем тестирования общедоступного интерфейса этих методов. Такой подход будет работать, но он не идеален. Лучше бы метод был в модуле, который был включен в контроллер. Тогда его можно будет протестировать и независимо от контроллера.
Брайан Хоган
9
Несмотря на то, что этот ответ технически отвечает на вопрос, я голосую против него, потому что он нарушает лучшие практики тестирования. Частные методы не следует тестировать, и то, что Ruby дает вам возможность обходить видимость методов, не означает, что вы должны злоупотреблять им.
Срджан Пежич
24
Срджан Пежич, не могли бы вы подробнее рассказать, почему не следует тестировать частные методы?
John Bachir
35
Я думаю, вы отрицательно оцениваете неправильные ответы или не отвечаете на вопрос. Это правильный ответ, и его нельзя отвергать. Если вы не согласны с практикой тестирования частных методов, поместите это в комментарии, так как это хорошая информация (как и многие), и тогда люди смогут проголосовать за этот комментарий, который по-прежнему показывает вашу точку зрения, без излишнего отрицания абсолютно правильного ответа.
traday 01
37

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

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

Потому что send может вызывать частные методы

graffzon
источник
Как бы вы тестировали переменные экземпляра в частном методе, используя .send?
the12
23

Где используется метод current_account? Какой цели это служит?

Как правило, вы не тестируете частные методы, а скорее тестируете методы, которые вызывают частный метод.

Райан Бигг
источник
5
В идеале следует проверить каждый метод. Я использовал как subject.send, так и subject.instance_eval с большим успехом в rspec
Дэвид В. Кейт
7
@Pullets Я не согласен, вы должны тестировать общедоступные методы API, которые будут вызывать частные, как сказано в моем исходном ответе. Вы должны тестировать API, который вы предоставляете, а не частные методы, которые видите только вы.
Райан Бигг
5
Я согласен с @Ryan Bigg. Вы не тестируете частные методы. Это лишает вас возможности рефакторинга или изменения реализации указанного метода, даже если это изменение не влияет на общедоступные части вашего кода. Ознакомьтесь с передовыми методами написания автоматических тестов.
Srdjan Pejic
4
Хм, может я что-то упускаю. В классах, которые я пишу, было намного больше частных методов, чем публичных. Тестирование только через общедоступный API приведет к появлению списка из сотен тестов, которые не отражают код, который они тестируют.
Дэвид В. Кейт
9
Насколько я понимаю, если вы хотите добиться реальной детализации в модульном тестировании, частные методы также должны быть протестированы. Если вы хотите провести рефакторинг кода, модульный тест также должен быть соответствующим образом переработан. Это гарантирует, что ваш новый код также работает должным образом.
Indika K
7

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

Это позволяет вам в дальнейшем изменять внутреннюю структуру вашего кода без изменения ваших тестов.

Lorem Ipsum Dolor
источник
4

Вы можете сделать свои частные или защищенные методы общедоступными:

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

Просто поместите этот код в свой тестовый класс, заменив свое имя класса. Включите пространство имен, если применимо.

zishe
источник
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
источник
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
barelyknown
источник
0

Если вам нужно протестировать частную функцию, создайте общедоступный метод, который вызывает частный.

liamfriel
источник
3
Я предполагаю, что вы имеете в виду, что это должно быть сделано в вашем коде модульного теста. По сути, это то, что делают .instance_eval и .send в одной строке кода. (И кто захочет писать более длинные тесты, когда более короткий дает тот же эффект?)
Дэвид В. Кейт
3
вздох, это контроллер рельсов. Метод должен быть приватным. Спасибо, что прочитали актуальный вопрос.
Майкл Джонстон
Вы всегда можете абстрагировать частный метод до общедоступного метода в объекте службы и ссылаться на него таким образом. Таким образом, вы можете тестировать только общедоступные методы и при этом сохранять свой код СУХИМ.
Джейсон
0

Я знаю, что это своего рода хакерство, но оно работает, если вы хотите, чтобы методы проверялись rspec, но не отображались в продукте.

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 
onetwopunch
источник