How to test model's callback method independently?

后端 未结 6 1124
感情败类
感情败类 2021-02-02 07:34

I had a method in a model:

class Article < ActiveRecord::Base
  def do_something
  end
end

I also had a unit test for this method:



        
相关标签:
6条回答
  • 2021-02-02 07:38
    describe "#do_something" do
    
     it "gives the article something" do
    
      @article = FactoryGirl.build(:article)
    
       expect(@article).to have_something
    
     @article.save
    end
    
    end
    
    0 讨论(0)
  • 2021-02-02 07:46

    In the spirit of Sandi Metz and minimalist testing, the suggestion in https://stackoverflow.com/a/16678194/2001785 to confirm the call to a possibly private method does not seem right to me.

    Testing a publicly-observable side-effect or confirming an outgoing command message makes more sense to me. Christian Rolle provided an example at http://www.chrisrolle.com/en/blog/activerecord-callback-tests-with-rspec.

    0 讨论(0)
  • 2021-02-02 07:49

    This is more of a comment than an answer, but I put it here for the syntax highlighting...

    I wanted a way to skip the callbacks in my tests, this is what I did. (This might help with the tests that broke.)

    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 讨论(0)
  • 2021-02-02 07:53

    I like to use ActiveRecord #run_callbacks method to make sure callbacks are been called without need to hit database. This way it runs faster.

    describe "#save" do
      let(:article) { FactoryBot.build(:article) }
      it "runs .do_something after save" do
        expect(article).to receive(:do_something)
        article.run_callbacks(:save)
      end
    end
    

    And to test the behavior of #do_something you add another test specifically for that.

    describe "#do_something" do
      let(:article) { FactoryBot.build(:article) }
      it "return thing" do
        expect(article.do_something).to be_eq("thing")
      end
    end
    
    0 讨论(0)
  • 2021-02-02 07:58

    Callback and Callback behavior are independent tests. If you want to check an after_save callback, you need to think of it as two things:

    1. Is the callback being fired for the right events?
    2. Is the called function doing the right thing?

    Assume you have the Article class with many callbacks, this is how you would test:

    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
    

    This decouples your callbacks from behavior. For example, you could trigger the same callback method article.do_something when some other related object is updated, say like user.before_save { user.article.do_something }. This will accomodate all those.

    So, keep testing your methods as usual. Worry about the callbacks separately.

    Edit: typos and potential misconceptions Edit: change "do something" to "trigger something"

    0 讨论(0)
  • 2021-02-02 07:59

    You can use shoulda-callback-matchers to test existence of your callbacks without calling them.

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

    If you also want to test the behaviour of the callback:

    describe Article do
      ...
    
      describe "#do_something" do
        it "gives the article something" do
          @article.save
          expect(@article).to have_something
        end
      end
    end
    
    0 讨论(0)
提交回复
热议问题