2011-08-26 12 views
48

All'interno del mio test voglio eseguire lo stub di una risposta predefinita per ogni istanza di una classe.Come faccio a mozzare le cose in MiniTest?

Potrebbe sembrare qualcosa di simile:

Book.stubs(:title).any_instance().returns("War and Peace") 

Poi ogni volta che io chiamo @book.title restituisce "Guerra e Pace".

C'è un modo per farlo all'interno di MiniTest? Se sì, puoi darmi un esempio di snippet di codice?

Oppure ho bisogno di qualcosa come la moka?

MiniTest supporta Mock ma Mock è eccessivo per quello che mi serve.

risposta

18

Io uso minitest per tutti i miei Gems testing, ma faccio tutti i miei stub con mocha, potrebbe essere possibile fare tutto in minitest con Mocks (non ci sono stub o altro, ma i mock sono piuttosto potenti), ma io trovare moka fa un grande lavoro, se aiuta:

require 'mocha'  
Books.any_instance.stubs(:title).returns("War and Peace") 
25

Se siete interessanti in modo semplice stubbing, senza una libreria beffarda, quindi è abbastanza facile da fare questo in Ruby:

class Book 
    def avg_word_count_per_page 
    arr = word_counts_per_page 
    sum = arr.inject(0) { |s,n| s += n } 
    len = arr.size 
    sum.to_f/len 
    end 

    def word_counts_per_page 
    # ... perhaps this is super time-consuming ... 
    end 
end 

describe Book do 
    describe '#avg_word_count_per_page' do 
    it "returns the right thing" do 
     book = Book.new 
     # a stub is just a redefinition of the method, nothing more 
     def book.word_counts_per_page; [1, 3, 5, 4, 8]; end 
     book.avg_word_count_per_page.must_equal 4.2 
    end 
    end 
end 

Se vuoi qualcosa di più complicato come lo stub di tutte le istanze di una classe, quindi è anche facile abbastanza da fare, devi solo per ottenere un po 'creativo:

class Book 
    def self.find_all_short_and_unread 
    repo = BookRepository.new 
    repo.find_all_short_and_unread 
    end 
end 

describe Book do 
    describe '.find_all_short_unread' do 
    before do 
     # exploit Ruby's constant lookup mechanism 
     # when BookRepository is referenced in Book.find_all_short_and_unread 
     # then this class will be used instead of the real BookRepository 
     Book.send(:const_set, BookRepository, fake_book_repository_class) 
    end 

    after do 
     # clean up after ourselves so future tests will not be affected 
     Book.send(:remove_const, :BookRepository) 
    end 

    let(:fake_book_repository_class) do 
     Class.new(BookRepository) 
    end 

    it "returns the right thing" do 
     # Stub #initialize instead of .new so we have access to the 
     # BookRepository instance 
     fake_book_repository_class.send(:define_method, :initialize) do 
     super 
     def self.find_all_short_and_unread; [:book1, :book2]; end 
     end 
     Book.find_all_short_and_unread.must_equal [:book1, :book2] 
    end 
    end 
end 
17

Si può facilmente stub metodi MiniTest. Le informazioni sono disponibili allo github.

Così, seguendo il tuo esempio, e utilizzando lo stile Minitest::Spec, questo è come si dovrebbe stub metodi:

# - RSpec - 
Book.stubs(:title).any_instance.returns("War and Peace") 

# - MiniTest - # 
Book.stub :title, "War and Peace" do 
    book = Book.new 
    book.title.must_equal "War and Peace" 
end 

Questo è un esempio veramente stupido, ma almeno ti dà un indizio su come fare quello che vuoi fare. Ho provato questo utilizzando MINITEST v2.5.1 che è la versione in bundle che viene fornito con Ruby 1.9 e sembra che in questa versione il metodo #stub non era ancora supportato, ma poi ho provato con MINITEST v3.0 e Ha funzionato come un fascino.

Buona fortuna e congratulazioni per l'utilizzo di MiniTest!

Edit: C'è anche un altro approccio per questo, e anche se sembra un po 'hacker, è ancora una soluzione al vostro problema:

klass = Class.new Book do 
    define_method(:title) { "War and Peace" } 
end 

klass.new.title.must_equal "War and Peace" 
+10

Sembra "Libro".stub: title, "War and Peace" 'funziona solo se' title' è una classe_metodo di 'Book' Non riesco a riprodurre lo stesso comportamento di' any_instance', l'errore 'NameError: metodo undefined' title 'per 'Book' ' – fguillen

+0

Titolo del metodo non definito 'per Book' – qichunren

+4

' stub' opera su 'metaclass = class << self; se stesso; end' come per [l'origine della libreria di simulazione] (https://github.com/seattlerb/minitest/blob/master/lib/minitest/mock.rb#L170) in MiniTest. Quindi sembra che tu possa solo eliminare i metodi singleton; Altrimenti vai con un oggetto finto completo. –

27
book = MiniTest::Mock.new 
    book.expect :title, "War and Piece" 

    Book.stub :new, book do 
    wp = Book.new 
    wp.title # => "War and Piece" 
    end 
+13

Trovo che sia davvero difficile da leggere. L'intento non è chiaro ... Anche se è più un commento su Minitest della tua risposta. –

+0

@StevenSoroka, [qui] (/ a/37991120/1136063) nella mia risposta, elaboro la risposta di panico. – MarkDBlackwell

12

Non si può fare questo con Minitest. Tuttavia, è possibile stub delle specialità stesse:

book = Book.new 
book.stub(:title, 'War and Peace') do 
    assert_equal 'War and Peace', book.title 
end 
3

si può sempre creare un modulo nel codice di test, e l'uso includere o estendere alle classi scimmia-di patch o oggetti con esso. ad esempio (in book_test.rb)

module BookStub 
    def title 
    "War and Peace" 
    end 
end 

Ora è possibile utilizzarlo nei test

describe 'Book' do 
    #change title for all books 
    before do 
    Book.include BookStub 
    end 
end 

#or use it in an individual instance 
it 'must be War and Peace' do 
    b=Book.new 
    b.extend BookStub 
    b.title.nust_equal 'War and Peace' 
end 

Questo permette di mettere insieme i comportamenti più complessi di un semplice stub potrebbe consentire

1

ho pensato di condividere un esempio che ho costruito sulle risposte qui.

Avevo bisogno di stubare un metodo alla fine di una lunga catena di metodi. Tutto è iniziato con una nuova istanza di un wrapper API PayPal. La chiamata avevo bisogno di stub era essenzialmente:

paypal_api = PayPal::API.new 
response = paypal_api.make_payment 
response.entries[0].details.payment.amount 

ho creato una classe che si è tornato a meno che il metodo era amount:

paypal_api = Class.new.tap do |c| 
    def c.method_missing(method, *_) 
    method == :amount ? 1.25 : self 
    end 
end 

Poi ho spegnevano in a PayPal::API:

PayPal::API.stub :new, paypal_api do 
    get '/paypal_payment', amount: 1.25 
    assert_equal 1.25, payments.last.amount 
end 

Si potrebbe fare questo lavoro per più di un metodo facendo un hash e restituendo hash.key?(method) ? hash[method] : self.

10

Per spiegare ulteriormente @ risposta di panico, supponiamo di avere una classe libro:

require 'minitest/mock' 
class Book; end 

In primo luogo, creare un'istanza stub Libro, e renderlo restituire il titolo desiderato (qualsiasi numero di volte):

book_instance_stub = Minitest::Mock.new 
def book_instance_stub.title 
    desired_title = 'War and Peace' 
    return_value = desired_title 
    return_value 
end 

Poi, rendono la classe Book istanziare il tuo libro esempio stub (solo e sempre, all'interno del blocco di codice seguente):

return_value = book_instance_stub 
method_to_redefine = :new 
Book.stub method_to_redefine, return_value do 

All'interno di questo blocco di codice (solo), il metodo Book :: new viene stubato. Proviamo:

some_book = Book.new 
    another_book = Book.new 
    puts some_book.title #=> "War and Peace" 
    puts some_book.title #=> "War and Peace" 
end 

Oppure, più conciso:

require 'minitest/mock' 
class Book; end 
instance = Minitest::Mock.new 
def instance.title() 'War and Peace' end 
Book.stub :new, instance do 
    book = Book.new 
    another_book = Book.new 
    puts book.title #=> "War and Peace" 
    puts book.title #=> "War and Peace" 
end 

In alternativa, è possibile installare l'estensione gemma Minitest 'Minitest-stub_any_instance'. (Nota: in questo modo, il metodo titolo Book # deve esistere prima di stub esso.) Ora, si può dire più semplicemente:

require 'minitest/stub_any_instance' 
class Book; def title() end end 
desired_title = 'War and Peace' 
Book.stub_any_instance :title, desired_title do 
    book = Book.new 
    another_book = Book.new 
    puts book.title #=> "War and Peace" 
    puts book.title #=> "War and Peace" 
end 

Se si desidera verificare che il titolo Book # viene richiamato un certo numero di volte , quindi eseguire:

require 'minitest/mock' 
class Book; end 

desired_title = 'War and Peace' 
return_value = desired_title 
method = :title 
book_instance_stub = Minitest::Mock.new 
number_of_title_invocations = 2 
number_of_title_invocations.times do 
    book_instance_stub.expect method, return_value 
end 

return_value = book_instance_stub 
method_to_redefine = :new 
Book.stub method_to_redefine, return_value do 
    some_book = Book.new 
    puts some_book.title #=> "War and Peace" 
    puts some_book.title #=> "War and Peace" 
end 
book_instance_stub.verify 

Così, per un particolare esempio, richiamando il metodo stubbed più volte quella specificata solleva MockExpectationError: No more expects available.

Inoltre, per qualsiasi istanza particolare, richiamando il metodo di stubato un numero di volte inferiore a quello specificato, viene generato il valore MockExpectationError: expected title(), ma solo se in seguito si richiama #verify su tale istanza.

Problemi correlati