How to test after_initialize callback of a rails model?
Asked Answered
H

3

8

I am using FactoryGirl and Rspec for testing. The model sets a foreign key after init if it is nil. Therefore it uses data of another association. But how can I test it? Normally I would use a factory for creation of this object and use a stub_chain for "self.user.main_address.country_id". But with the creation of this object, after initialize will be invoked. I have no chance to stub it.

after_initialize do
  if self.country_id.nil?
    self.country_id = self.user.main_address.country_id || Country.first.id
  end
end

Any idea?

Howe answered 13/9, 2011 at 7:47 Comment(0)
A
9

Ideally it's better that you test behavior instead of implementation. Test that the foreign key gets set instead of testing that the method gets called.

Although, if you want to test the after_initialize callback here is a way that works.

obj = Model.allocate
obj.should_receive(:method_here)
obj.send(:initialize)

Allocate puts the object in memory but doesn't call initialize. After you set the expectation, then you can call initialize and catch the method call.

Acuff answered 24/4, 2012 at 20:36 Comment(1)
This method doesn't seem to work anymore with Rails 6 and rspec-rails 4.0.2. It throws "Cannot proxy frozen objects, rspec-mocks relies on proxies for method stubbing and expectations.". The newer answer from @Yoopergeek worked though.Iphlgenia
D
7

Orlando's method works, and here's another which I'd like to add. (Using new rspec 'expect' syntax)

expect_any_instance_of(Model).to receive(:method_here)
Model.new
Defeatism answered 31/1, 2016 at 0:11 Comment(0)
A
0

Another approach is to refactor your code to allow simple unit tests. As is, your code is approaching the upper end of how much code I'd put in a callback:

after_initialize do
  if self.country_id.nil?
    self.country_id = self.user.main_address.country_id || Country.first.id
  end
end

If it grew much more, I'd extract it to a method and reduce your callback to a method call:

after_initialize :init_country_id

def init_country_id 
  if self.country_id.nil?
    self.country_id = self.user.main_address.country_id || Country.first.id
  end
end

The bonus here is that testing init_country_id becomes just another method unit test at this point...nothing fancy about that.

Now that you've got a unit test on the behavior, you can also test that it gets called, if you're in doubt. (Something as simple as after_initialize :init_country_id does not need invocation testing, IMO)

You can use gem shoulda-callback-matchers to test that your callbacks are actually getting triggered as intended:

 it { is_expected.to callback(:init_country_id).before(:initialize) }
Am‚lie answered 23/7, 2019 at 2:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.