我在模型中有一个方法:
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
也会触发此方法,或者我会遇到重复的数据库操作。create
更改为build
使用常规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
测试通过,但我担心的是它不再是具体的方法。如果添加更多,效果将与其他回调混合。
我的问题是,有没有什么好方法可以测试模型的实例方法独立成为回调?
答案 0 :(得分:66)
回调和回调行为是独立测试。如果要检查after_save回调,则需要将其视为两件事:
假设你有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 }
。这将适应所有这些。
所以,像往常一样继续测试你的方法。分别担心回调。
编辑:错别字和潜在的误解 编辑:将“做某事”改为“触发某事”
答案 1 :(得分:16)
您可以使用shoulda-callback-matchers来测试回调的存在而无需调用它们。
describe Article do
it { should 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
答案 2 :(得分:1)
我喜欢使用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
答案 3 :(得分: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
答案 4 :(得分:0)
按照Sandi Metz和极简主义者testing的精神,https://stackoverflow.com/a/16678194/2001785中关于确认调用可能为私有方法的建议对我来说似乎不正确。
测试对公众可见的副作用或确认传出的命令消息对我来说更有意义。克里斯蒂安·罗尔(Christian Rolle)在http://www.chrisrolle.com/en/blog/activerecord-callback-tests-with-rspec提供了一个示例。
答案 5 :(得分:-1)
describe "#do_something" do
it "gives the article something" do
@article = FactoryGirl.build(:article)
expect(@article).to have_something
@article.save
end
end