web-dev-qa-db-fra.com

Comment tester la méthode de rappel du modèle indépendamment?

J'ai eu une méthode dans un modèle:

class Article < ActiveRecord::Base
  def do_something
  end
end

J'ai également eu un test unitaire pour cette méthode:

# spec/models/article_spec.rb
describe "#do_something" do
  @article = FactoryGirl.create(:article)
  it "should work as expected" do
    @article.do_something
    expect(@article).to have_something
  end
  # ...several other examples for different cases
end

Tout allait bien jusqu'à ce que je trouve qu'il est préférable de déplacer cette méthode dans un callback after_save:

class Article < ActiveRecord::Base
  after_save :do_something

  def do_something
  end
end

Maintenant, tous mes tests sur cette méthode ont échoué. Je dois le réparer en:

  • Aucun appel plus spécifique à do_something car create ou save déclenchera également cette méthode, ou je rencontrerai des actions de base de données en double.
  • Remplacez create par build
  • Testez respond_to
  • Utilisez le model.save général au lieu de l'appel de méthode individuel model.do_something

    describe "#do_something" do
      @article = FactoryGirl.build(:article)
      it "should work as expected" do
        expect{@article.save}.not_to raise_error
        expect(@article).to have_something
        expect(@article).to respond_to(:do_something)
      end
    end
    

Le test a réussi, mais ce qui me préoccupe, c’est qu’il ne s’agit plus de la méthode spécifique. L'effet sera mélangé avec d'autres rappels si plus est ajouté

Ma question est la suivante: existe-t-il un moyen magnifique de tester les méthodes d'instance d'un modèle de manière indépendante pour devenir un rappel?

31
Billy Chan

Les comportements de rappel et de rappel sont des tests indépendants. Si vous souhaitez vérifier un rappel after_save, vous devez le considérer comme deux choses:

  1. Le rappel est-il déclenché pour les bons événements?
  2. La fonction appelée fait-elle la bonne chose?

Supposons que vous ayez la classe Article avec de nombreux rappels, voici comment vous testeriez:

class Article < ActiveRecord::Base
  after_save    :do_something
  after_destroy :do_something_else
  ...
end

it "triggers do_something on save" do
  expect(@article).to receive(:do_something)
  @article.save
end

it "triggers do_something_else on destroy" do
  expect(@article).to receive(:do_something_else)
  @article.destroy
end

it "#do_something should work as expected" do
  # Actual tests for do_something method
end

Cela dissocie vos rappels du comportement. Par exemple, vous pouvez déclencher la même méthode de rappel article.do_something lorsqu'un autre objet associé est mis à jour, par exemple user.before_save { user.article.do_something }. Cela va accueillir tous ceux.

Alors, continuez à tester vos méthodes comme d’habitude. S'inquiéter des rappels séparément.

Éditer: fautes de frappe et idées fausses potentielles .__ Éditer: changer "faire quelque chose" pour "déclencher quelque chose"

64
Subhas

Vous pouvez utiliser shoulda-callback-matchers pour tester l'existence de vos rappels sans les appeler.

describe Article do
  it { should callback(:do_something).after(:save) }
end

Si vous souhaitez également tester le comportement du rappel: 

describe Article do
  ...

  describe "#do_something" do
    it "gives the article something" do
      @article.save
      expect(@article).to have_something
    end
  end
end
14
Filip Bartuzi

Ceci est plus un commentaire qu'une réponse, mais je le mets ici pour la coloration syntaxique ...

Je voulais un moyen de passer les callbacks dans mes tests, c'est ce que j'ai fait. (Cela pourrait aider avec les tests qui ont éclaté.)

class Article < ActiveRecord::Base
  attr_accessor :save_without_callbacks
  after_save :do_something

  def do_something_in_db
    unless self.save_without_callbacks
      # do something here
    end
  end
end

# spec/models/article_spec.rb
describe Article do
  context "after_save callback" do
    [true,false].each do |save_without_callbacks|
      context "with#{save_without_callbacks ? 'out' : nil} callbacks" do
        let(:article) do
          a = FactoryGirl.build(:article)
          a.save_without_callbacks = save_without_callbacks
        end
        it do
          if save_without_callbacks
            # do something in db
          else
            # don't do something in db
          end
        end
      end
    end
  end
end
0
Teddy

Dans l’esprit de Sandi Metz et de minimaliste testing , la suggestion de https://stackoverflow.com/a/16678194/2001785 de confirmer l’appel à une méthode éventuellement privée ne me semble pas juste.

Tester un effet secondaire observable publiquement ou confirmer un message de commande sortant me semble plus logique. Christian Rolle a donné un exemple à http://www.chrisrolle.com/fr/blog/activerecord-callback-tests-with-rspec .

0
user2001785