Rspec, Rails: bagaimana cara menguji metode privat pengontrol?

125

Saya memiliki pengontrol:

class AccountController < ApplicationController
  def index
  end

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

Bagaimana cara menguji metode privat current_accountdengan rspec?

PS Saya menggunakan Rspec2 dan Ruby on Rails 3

petRUShka
sumber
8
Ini tidak menjawab pertanyaan Anda, tetapi metode privat tidak seharusnya diuji. Pengujian Anda seharusnya hanya memperhatikan hal yang nyata - API publik Anda. Jika metode publik Anda berfungsi, metode pribadi yang mereka sebut juga berfungsi.
Samy Dindane
77
Saya tidak setuju. Ada nilai dalam menguji fitur yang cukup kompleks dalam kode Anda.
whitehat101
11
Saya juga tidak setuju. Jika API publik Anda berfungsi, Anda hanya dapat mengasumsikan metode pribadi Anda berfungsi seperti yang diharapkan. Tetapi spesifikasi Anda mungkin lewat secara kebetulan.
Rimian
4
Akan lebih baik untuk mengekstrak metode privat ke kelas baru yang dapat diuji, jika metode privat perlu diuji.
Kris
10
@RonLge Anda benar. Dengan lebih banyak melihat ke belakang dan pengalaman, saya tidak setuju dengan komentar saya selama tiga tahun. :)
Samy Dindane

Jawaban:

196

Gunakan #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
kacamata berlensa tunggal
sumber
94
Jika mau, Anda juga dapat mengucapkan: @ controller.send (: current_account).
Kebingungan
13
Ruby memungkinkan Anda memanggil metode privat dengan send, tetapi itu tidak berarti Anda harus melakukannya. Pengujian metode privat dilakukan melalui pengujian antarmuka publik ke metode tersebut. Pendekatan ini akan berhasil, tetapi itu tidak ideal. Akan lebih baik jika metode tersebut ada dalam modul yang dimasukkan ke dalam pengontrol. Kemudian bisa diuji secara independen dari pengontrol juga.
Brian Hogan
9
Meskipun, jawaban ini secara teknis menjawab pertanyaan, saya merendahkannya, karena melanggar praktik terbaik dalam pengujian. Metode privat tidak boleh diuji, dan hanya karena Ruby memberi Anda kemampuan untuk menghindari visibilitas metode, itu tidak berarti Anda harus menyalahgunakannya.
Srdjan Pejic
24
Srdjan Pejic Bisakah Anda menjelaskan mengapa metode privat tidak perlu diuji?
John Bachir
35
Saya pikir Anda tidak menyukai jawaban yang salah atau tidak menjawab pertanyaan itu. Jawaban ini benar dan tidak boleh diremehkan. Jika Anda tidak setuju dengan praktik pengujian metode privat, letakkan di komentar karena ini adalah info yang baik (seperti yang dilakukan banyak orang) dan kemudian orang-orang dapat memberi suara positif pada komentar itu, yang masih menunjukkan maksud Anda tanpa perlu merendahkan jawaban yang valid secara prefek.
Traday
37

Saya menggunakan metode kirim. Misalnya:

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

Karena "kirim" dapat memanggil metode pribadi

graffzon.dll
sumber
Bagaimana Anda menguji variabel instance dalam metode privat menggunakan .send?
the12
23

Di mana metode current_account digunakan? Apa tujuannya?

Umumnya, Anda tidak menguji metode privat melainkan menguji metode yang memanggil metode privat.

Ryan Bigg
sumber
5
Idealnya seseorang harus menguji setiap metode. Saya telah menggunakan subject.send dan subject.instance_eval dengan banyak keberhasilan dalam rspec
David W. Keith
7
@Pullets Saya tidak setuju, Anda harus menguji metode publik dari API yang akan memanggil metode pribadi, seperti yang dikatakan jawaban asli saya. Anda harus menguji API yang Anda berikan, bukan metode pribadi yang hanya Anda lihat.
Ryan Bigg
5
Saya setuju dengan @Ryan Bigg. Anda tidak menguji metode pribadi. Ini menghilangkan kemampuan Anda untuk memfaktor ulang atau mengubah implementasi metode tersebut, meskipun perubahan itu tidak memengaruhi bagian publik dari kode Anda. Harap baca tentang praktik terbaik saat menulis tes otomatis.
Srdjan Pejic
4
Hmm, mungkin saya melewatkan sesuatu. Kelas-kelas yang saya tulis memiliki lebih banyak metode privat daripada kelas umum. Menguji hanya melalui API publik akan menghasilkan daftar ratusan pengujian yang tidak mencerminkan kode yang mereka uji.
David W. Keith
9
Menurut pemahaman saya jika Anda ingin mencapai perincian nyata dalam pengujian unit metode pribadi juga harus diuji. Jika Anda ingin menguji unit kode refactor juga harus refactored sesuai. Ini memastikan kode baru Anda juga berfungsi seperti yang diharapkan.
Indika K
7

Seharusnya tidak menguji metode privat Anda secara langsung, metode tersebut dapat dan harus diuji secara tidak langsung dengan menggunakan kode dari metode publik.

Ini memungkinkan Anda untuk mengubah internal kode Anda tanpa harus mengubah pengujian Anda.

Lorem Ipsum Dolor
sumber
4

Anda dapat menjadikan Anda metode pribadi atau dilindungi sebagai publik:

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

Letakkan saja kode ini di kelas pengujian Anda yang menggantikan nama kelas Anda. Sertakan namespace jika ada.

zishe
sumber
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
sumber
1

Metode pribadi pengujian unit tampaknya terlalu di luar konteks dengan perilaku aplikasi.

Apakah Anda menulis kode panggilan Anda terlebih dahulu? Kode ini tidak disebut dalam contoh Anda.

Perilakunya adalah: Anda ingin sebuah objek dimuat dari objek lain.

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

Mengapa Anda ingin menulis tes di luar konteks dari perilaku yang harus Anda coba gambarkan?

Apakah kode ini digunakan di banyak tempat? Butuh pendekatan yang lebih umum?

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

Brent Greeff
sumber
1

Gunakan permata rspec-context-private untuk sementara membuat metode privat menjadi publik dalam suatu konteks.

gem 'rspec-context-private'

Ini berfungsi dengan menambahkan konteks bersama ke proyek Anda.

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

Kemudian, jika Anda mengirimkan :privatemetadata ke describeblok, metode privat akan menjadi publik dalam konteks itu.

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

Jika Anda perlu menguji fungsi privat buat metode publik yang memanggil privat.

liamfriel
sumber
3
Saya berasumsi bahwa maksud Anda ini harus dilakukan dalam kode pengujian unit Anda. Pada dasarnya itulah yang dilakukan .instance_eval dan .send dalam satu baris kode. (Dan siapa yang ingin menulis tes yang lebih panjang ketika tes yang lebih pendek memiliki efek yang sama?)
David W. Keith
3
menghela napas, itu pengontrol rel. Metode tersebut harus pribadi. Terima kasih telah membaca pertanyaan sebenarnya.
Michael Johnston
Anda selalu dapat mengabstraksi metode privat menjadi metode publik dalam objek layanan dan mereferensikannya dengan cara itu. Dengan cara itu Anda hanya dapat menguji metode publik, namun tetap menjaga kode Anda KERING.
Jason
0

Saya tahu ini agak hacky, tetapi berfungsi jika Anda ingin metode yang dapat diuji oleh rspec tetapi tidak terlihat di 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

Sekarang ketika Anda dapat menjalankan spesifikasi Anda seperti ini:

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