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:
describe "#do_something" do
it "gives the article something" do
@article = FactoryGirl.build(:article)
expect(@article).to have_something
@article.save
end
end
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.
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
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
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:
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"
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