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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With