web-dev-qa-db-fra.com

Comment écraser des éléments dans MiniTest?

Dans mon test, je veux bloquer une réponse standard pour toute instance d'une classe.

Cela pourrait ressembler à quelque chose comme:

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

Ensuite, chaque fois que j'appelle @book.title il renvoie "Guerre et paix".

Existe-t-il un moyen de le faire dans MiniTest? Si oui, pouvez-vous me donner un exemple d'extrait de code?

Ou ai-je besoin de quelque chose comme du moka?

MiniTest prend en charge les Mocks mais les Mocks sont exagérés pour ce dont j'ai besoin.

59
Nick
  # Create a mock object
  book = MiniTest::Mock.new
  # Set the mock to expect :title, return "War and Piece"
  # (note that unless we call book.verify, minitest will
  # not check that :title was called)
  book.expect :title, "War and Piece"

  # Stub Book.new to return the mock object
  # (only within the scope of the block)
  Book.stub :new, book do
    wp = Book.new # returns the mock object
    wp.title      # => "War and Piece"
  end
33
Panic

Si vous êtes intéressé par le stubbing simple sans bibliothèque moqueuse, il est assez facile de le faire dans 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

Si vous voulez quelque chose de plus compliqué comme écraser toutes les instances d'une classe, alors c'est aussi assez facile à faire, il vous suffit d'être un peu créatif:

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
26
Elliot Winkler

J'utilise minitest pour tous mes tests Gems, mais je fais tous mes talons avec du moka, il pourrait être possible de tout faire en minitest avec Mocks (il n'y a pas de stubs ou autre chose, mais les mocks sont assez puissants), mais je trouve que mocha fait un excellent travail, si cela aide:

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

Vous pouvez facilement stub les méthodes dans MiniTest. Les informations sont disponibles sur github .

Donc, en suivant votre exemple et en utilisant le Minitest::Spec style, voici comment vous devez stub les méthodes:

# - 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

C'est un exemple vraiment stupide mais au moins vous donne un indice sur la façon de faire ce que vous voulez faire. J'ai essayé ceci en utilisant MiniTest v2.5.1 qui est la version fournie avec Ruby 1.9 et il semble que dans cette version la méthode #stub n'était pas encore pris en charge, mais j'ai essayé avec MiniTest v3.0 et cela a fonctionné comme un charme.

Bonne chance et félicitations pour avoir utilisé MiniTest!

Edit: Il existe également une autre approche pour cela, et même si cela semble un peu hackish, c'est toujours une solution à votre problème:

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

klass.new.title.must_equal "War and Peace"
19
mongrelion

Vous ne pouvez pas faire cela avec Minitest. Cependant, vous pouvez bloquer n'importe quelle instance particulière:

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

Juste pour expliquer davantage réponse de @ panic , supposons que vous ayez une classe Book:

require 'minitest/mock'
class Book; end

Tout d'abord, créez un talon d'instance Book et faites-le retourner le titre souhaité (autant de fois que vous le souhaitez):

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

Ensuite, faites en sorte que la classe Book instancie votre stub d'instance Book (uniquement et toujours, dans le bloc de code suivant):

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

Dans ce bloc de code (uniquement), la méthode Book::new Est tronquée. Essayons:

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

Ou, plus laconiquement:

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"
end

Alternativement, vous pouvez installer la gemme d'extension Minitest minitest-stub_any_instance. (Remarque: lorsque vous utilisez cette approche, la méthode Book#title Doit exister avant de la bloquer.) Maintenant, vous pouvez dire plus simplement:

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"
end

Si vous souhaitez vérifier que Book#title Est invoqué un certain nombre de fois, procédez comme suit:

require 'minitest/mock'
class Book; end

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

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

Ainsi, pour toute instance particulière, l'invocation de la méthode stubbed plus de fois que spécifié augmente MockExpectationError: No more expects available.

De plus, pour toute instance particulière, avoir invoqué la méthode stubbed moins de fois que spécifié augmente MockExpectationError: expected title(), mais uniquement si vous appelez #verify Sur cette instance à ce stade.

14
MarkDBlackwell

Vous pouvez toujours créer un module dans votre code de test et utiliser l'inclusion ou l'extension à des classes ou des objets de patch de singe avec lui. par exemple (dans book_test.rb)

module BookStub
  def title
     "War and Peace"
  end
end

Vous pouvez maintenant l'utiliser dans vos tests

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.must_equal 'War and Peace'
 end

Cela vous permet de mettre en place des comportements plus complexes qu'un simple stub pourrait permettre

5

J'ai pensé partager un exemple que j'ai construit sur les réponses ici.

J'avais besoin de stub une méthode à la fin d'une longue chaîne de méthodes. Tout a commencé avec une nouvelle instance d'un wrapper API Paypal. L'appel dont j'avais besoin pour aboutir était essentiellement:

Paypal_api = Paypal::API.new
response = Paypal_api.make_payment
response.entries[0].details.payment.amount

J'ai créé une classe qui s'est retournée à moins que la méthode ne soit amount:

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

Ensuite, je l'ai inséré dans Paypal::API:

Paypal::API.stub :new, Paypal_api do
  get '/Paypal_payment', amount: 1.25
  assert_equal 1.25, payments.last.amount
end

Vous pouvez faire en sorte que cela fonctionne pour plusieurs méthodes en créant un hachage et en renvoyant hash.key?(method) ? hash[method] : self.

1
Eric Boehs