Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test before_update callback in rails model with Rspec and FactoryGirl?

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?

like image 354
Luiz Henrique Avatar asked Nov 29 '22 22:11

Luiz Henrique


2 Answers

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.

like image 191
zetetic Avatar answered Dec 07 '22 00:12

zetetic


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.

like image 26
VAD Avatar answered Dec 07 '22 01:12

VAD