Reputation: 957
I am trying to test the before_update callback of the model bellow.
models/option.rb:
class Option < ApplicationRecord
belongs_to :activity
has_many :suboptions, class_name: "Option", foreign_key: "option_id"
belongs_to :parent, class_name: "Option", optional: true, foreign_key: "option_id"
accepts_nested_attributes_for :suboptions, allow_destroy: true,
reject_if: ->(attrs) { attrs['name'].blank? }
validates :name, presence: true
before_create :set_defaults
before_update :set_updates
def set_defaults
self.suboptions.each do |sbp|
sbp.activity_id = self.activity_id
end
end
def set_updates
suboptions.each do |child|
child.activity_id = self.activity_id
end
end
end
spec/models/option.rb:
require 'rails_helper'
RSpec.describe Option, type: :model do
describe "Callbacks" do
it "before_create" do
suboption = create(:option)
option = create(:option, suboptions:[suboption])
option.run_callbacks(:create) {false}
expect(option.suboptions.first.activity_id).to eq suboption.activity_id
end
it "before_update" do
end
end
end
I successfully tested the before_create callback (at least it gave me the correct result). But I don't know how to test the before_update callback. Is there a way to do it?
Upvotes: 4
Views: 5245
Reputation: 957
I got a solution using run_callbacks
. I created an option and a suboption. Then I updated the activity_id of the option and used option.run_callbacks(:update) {false}
to run before_update
callback (if I used {true}
, it would run before_update
and after_update
callbacks):
it "before_update" do
suboption = create(:option)
option = create(:option, suboptions:[suboption])
option.update(activity_id: 5)
option.run_callbacks(:update) {false}
expect(option.suboptions.first.activity_id).to eq option.activity_id
end
If I do not use the option.run_callbacks(:update) {false}
, the expect expression gets a different activity_id for option and suboption. But by using the code like that, the test runs correctly and option and suboption have the same activity_id.
Upvotes: 2
Reputation: 47548
Warning: this answer is opinionated.
Test behavior, not implementation.
A callback is an implementation detail. Don't test it directly. Instead, pretend that you don't know how the model works internally, and test how it behaves.
If I'm reading the code correctly, the behavior can be described like so:
When updating an option, the activity_id of each of its suboptions is set to the activity_id of the option.
Create an option with suboptions. Update it, reload it, and check that the value of each activity_id is correct.
This will be slower than mocking, but less brittle. Also, the test is much easier to write and maintain.
Upvotes: 12
Reputation: 2401
Ok. I'll try to start from the beginning.
To test callback you have to test that it would be called when it should. That's all.
You may want to test the code of the method exactly. But such methods usually are private and they should be private indeed. And you shouldn't test private methods' code at all. If you want to do it anyway, your test will be coupled to your private methods and that's not good.
You can test before_update :set_updates
like this:
let(:option) { Option.create("init your params here") }
it "test callback" do
expect(option).to receive(:set_updates)
option.save
end
If you want to test the code of your private method, you can do that like this
let(:option) { Option.create("init your params here") }
it "test callback" do
# expect to receive some messages
# which are in your method code
# for example
expect_any_instance_of(Suboption).to receive(:activity_id=)
option.send(:set_updates)
end
P.S. You may want to watch/listen "Rails Conf 2013 The Magic Tricks of Testing by Sandi Metz". It's very helpful thing.
Upvotes: 7