views:

24

answers:

1

I have some nested models that require a bit more than the standard accepts_nested_attributes_for logic. Instead of automatically creating or updating child records based on id key, in this case the child records must already exist and have certain other conditions, or it raises an error.

So as part of this, I have a parent model iterating through the given children attributes and calling #update_attributes on the child records that match.

My question is how to mock the #update_attributes in the specs for the parent model. I want to test that the child record(s) identified by the given attributes are in fact the one(s) that receives #update_attributes

My first approach was to try mock_model, something like this (not using the actual model names but simply 'subject' and 'children' for clarity):

before :each do
  subject.children = mock_model(Child), mock_model(Child), mock_model(Child)
  subject.save!
  @expected = subject.children[1]
  @input = {:child_id => @expected.id, :value => 'something'}
end

it 'should call #update_attributes on the child with given attributes' do
  @expected.should_receive(:update_attributes).
    with({:value => 'something'})
  subject.merge_children_attributes(@input)
end

However, you can't actually persist mock models. And you can't use stub models either for the same reason. And I need persisted records since I use children.find() or children.all() in the implementation. (unless I mocked the children association, which doesn't seem right given it's a method of the subject).

So then I went to fixtures.

fixtures :children

before :each do
  subject.children = children(:one), children(:two), children(:three)
  subject.save!
  @expected = subject.children[1]
  @input = {:child_id => @expected.id, :value => 'something'}
end

it 'should call #update_attributes on the child with given attributes' do
  @expected.should_receive(:update_attributes).
    with({:value => 'something'})
  subject.merge_children_attributes(@input)
end

The problem here is that the expected matched record (@expected) never receives :update_attributes -- with anything.

With much putsing around, the reason for this is that when the implementation calls children.find(), it returns a different instance than the @expected = subject.children[1] in the spec. Although the :id attribute is the same. ActiveRecord is no identity map...

So how would I write this spec to pass?

UPDATE

I confirmed looking at the log that the correct record is in fact being updated. So it's frustrating that I can't seem to mock :update_attributes because of object identity not being maintained between finds.

A: 

Not really a satisfying answer, but what I did to pass the spec was stub out Child.find (which is the underlying call when you do a find on the children association) -- to return an array of mock children that I could set expectations on. So:

before :each do
  @children = [ mock_model(Child), mock_model(Child), mock_model(Child) ]
  Child.should_receive(:find).and_return(@children)
  @expected= @children[1]
  @input = {:child_id => @expected.id, :value => 'something'}
end

it 'should call #update_attributes on the child with given attributes' do
  @expected.should_receive(:update_attributes).
    with({:value => 'something'})
  subject.merge_children_attributes(@input)
end

Not that satisfying, because expecting exactly one children.find(:all) in the implementation seems too much of an assumption to put in a spec. Not likely, but who's to say there couldn't be an implementation that calls multiple children.find()s instead ?

Eric G