40

我在模型中有一个方法:

class Article < ActiveRecord::Base
  def do_something
  end
end

我也对这个方法进行了单元测试:

# 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

一切都很好,直到我发现最好将此方法移动到after_save回调中:

class Article < ActiveRecord::Base
  after_save :do_something

  def do_something
  end
end

现在我所有关于这种方法的测试都被打破了。我必须通过以下方式修复它:

  • 没有更具体的调用,do_something因为create或者save也将触发此方法,否则我将遇到重复的数据库操作。
  • 更改createbuild
  • 测试 response_to
  • 使用通用model.save而不是单独的方法调用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
    

测试通过了,但我担心的是它不再是具体的方法。如果添加更多,效果将与其他回调混合

我的问题是,有没有什么漂亮的方法可以独立地测试模型的实例方法,而不是成为回调?

4

6 回答 6

85

回调和回调行为是独立的测试。如果要检查 after_save 回调,则需要将其视为两件事:

  1. 是否为正确的事件触发回调?
  2. 被调用的函数做对了吗?

假设您有Article许多回调的类,这就是您将如何测试:

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

这将您的回调与行为分离。例如,article.do_something当其他一些相关对象被更新时,您可以触发相同的回调方法,比如user.before_save { user.article.do_something }. 这将容纳所有这些。

所以,像往常一样继续测试你的方法。分别担心回调。

编辑:错别字和潜在的误解编辑:将“做某事”改为“触发某事”

于 2013-05-21T19:43:32.610 回答
17

您可以使用shoulda-callback-matchers来测试您的回调是否存在而不调用它们。

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

如果您还想测试回调的行为:

describe Article do
  ...

  describe "#do_something" do
    it "gives the article something" do
      @article.save
      expect(@article).to have_something
    end
  end
end
于 2014-08-28T15:56:45.913 回答
3

我喜欢使用 ActiveRecord #run_callbacks 方法来确保调用回调而不需要访问数据库。这样它运行得更快。

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

为了测试#do_something 的行为,您需要专门为此添加另一个测试。

describe "#do_something" do
  let(:article) { FactoryBot.build(:article) }
  it "return thing" do
    expect(article.do_something).to be_eq("thing")
  end
end
于 2020-06-30T19:59:35.207 回答
2

本着 Sandi Metz 和极简测试的精神, https://stackoverflow.com/a/16678194/2001785中确认调用可能是私有方法的建议对我来说似乎不合适。

测试可公开观察的副作用或确认传出的命令消息对我来说更有意义。Christian Rolle 在http://www.chrisrolle.com/en/blog/activerecord-callback-tests-with-rspec提供了一个示例。

于 2018-08-15T15:41:52.640 回答
0

这更像是一个评论而不是一个答案,但我把它放在这里是为了语法突出显示......

我想要一种方法来跳过测试中的回调,这就是我所做的。(这可能有助于测试失败。)

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
于 2013-05-21T19:30:43.573 回答
-1
describe "#do_something" do

 it "gives the article something" do

  @article = FactoryGirl.build(:article)

   expect(@article).to have_something

 @article.save
end

end
于 2015-07-23T08:08:58.397 回答