我在模型中有一个方法:
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
也会触发这个方法,否则我会遇到重复的db动作。 - 将
create
更改为build
测试respond_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
测试通过了,但我担心的是它不再是关于具体的方法。如果添加了更多的,则效果将是与其他回调混合。
我的问题是,是否有任何漂亮的方法来测试模型的实例方法独立成为回调?
回调和回调行为是独立的测试。如果你想检查一个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
这将从行为中分离回调。例如,可以在更新其他相关对象(例如user.before_save { user.article.do_something }
)时触发相同的回调方法article.do_something
。这将容纳所有这些
所以,继续像往常一样测试你的方法。单独考虑回调
编辑:错别字和潜在的误解编辑:将"do something"更改为"trigger something"您可以使用shoulda-callback匹配器来测试回调是否存在,而无需调用它们。
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
我喜欢使用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
本着Sandi Metz和极简测试的精神,https://stackoverflow.com/a/16678194/2001785中确认调用可能的私有方法的建议对我来说似乎是不对的。
对我来说,测试一个公开可见的副作用或确认一个传出的命令消息更有意义。Christian Rolle在http://www.chrisrolle.com/en/blog/activerecord-callback-tests-with-rspec提供了一个例子。这与其说是回答,不如说是评论,但我把它放在这里是为了语法高亮显示…
我想要一种在测试中跳过回调的方法,这就是我所做的。(这可能有助于测试失败。)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
describe "#do_something" do
it "gives the article something" do
@article = FactoryGirl.build(:article)
expect(@article).to have_something
@article.save
end
end