How to mock active record relationships

Hi
I am new to Rspec and try to mock my controller that looks like

def create
@friendship = current_user.friendships.build(:friend_id =>
params[:friend_id])
if @friendship.save
flash[:notice] = “Added friend.”
render :text => flash[:notice]
else
flash[:error] = “Error occurred when adding friend.”
render :text => flash[:notice]
end
end

how can I mock
current_user.friendships.build ?

I tried

before(:each) do
@friend_ship = mock_model(Friendship, :friend_id => 1947284801,
:user_id => 134245544)
controller.stub!(:current_user).should_receive(‘friendships’).and_return(@friend_ship)
end

but get
You have a nil object when you didn’t expect it! The error occurred
while evaluating nil.friendship

user = mock(‘user’)
controller.stub(:current_user).and_return user
friendships_proxy = mock(‘friendships proxy’)
user.stub(:friendships).and_return friendships_proxy
friendship = mock(‘friendship’)
friendhips_proxy.stub(:build).and_return friendship

Try to avoid chains like this. Makes the test setup noisy, and the
test brittle. One thing I like to do is wrap up the association chain
in a method and stub that. So it would look something like:

class FriendshipsController < ApplicationController
def create
@friendship = build_friendship

end

def build_friendship
current_user.friendships.build(:friend_id => params[:friend_id])
end
end

then the mock setup becomes:

friendship = mock(‘friendship’)
controller.stub(:build_friendship).and_return friendship

Typically you do not want to mock the object under test but my
experience has been that this is one situation where it pays to break
the rules. AR encourages a style that is not particularly friendly to
unit testing. With this pattern you can have your cake and eat it
too.


A couple other notes:

  1. I’m not a fan of object.collection.build. If save fails, then the
    built object is still in the in-memory collection. If you iterate
    through that anywhere on the page, you’ll use/render an invalid
    record. I see people use object.collection.build all the time and
    frankly don’t understand why it doesn’t bite them more frequently. I
    guess they just don’t iterate over the collection on the “new object”
    page that often

  2. You might consider creating a Friendship object directly, something
    like Friendship.create(:members => [current_user,
    User.find(params[:friend_id]). Really easy to test because you don’t
    need to stub a huge association chain, you can just expect one call to
    Friendship.create. From a design perspective, neither user object has
    more importance than the other one, so why is the current user given
    priority in the code? It’s not a strong argument in this case, but
    the same sorta thing crops up all the time. A classic example is a
    transfer in between bank accounts - does a bank account know how to
    transfer money to another account, or is there a bank or banker object
    that knows how to do a transfer between accounts? Or maybe you create
    a transfer transaction and post it to one or more ledgers. As with
    anything, there are a number of potential ways you could model it.
    One thing I’ve noticed is that AR gives you the ability to write
    highly readable code, but in doing so people tend to overlook the
    subtleties of their domain model.

  3. Using a framework like resource_controller will drastically reduce
    the number of controller specs you need to write

Pat

On 21 Oct 2009, at 06:40, Christoph ---- wrote:

 flash[:error] = "Error occurred when adding friend."

@friend_ship = mock_model(Friendship, :friend_id => 1947284801,
:user_id => 134245544)
controller.stub!(:current_user).should_receive
(‘friendships’).and_return(@friend_ship)
end

but get
You have a nil object when you didn’t expect it! The error occurred
while evaluating nil.friendship

Hi Christoph ----

You’re making three mistakes:
(1) confusing the collection with the model (friendships vs friendship)
(2) using the wrong stub syntax (I think - I don’t actually chain
calls after stub together like that)
(3) putting an expectation in the before block

If you really want to do it this way, you’d have to write something
along these lines:

describe User do
before(:each) do
@friendship = mock_model(
Friendship, :friend_id => 1947284801, :user_id => 134245544
)
# Issue (1)
@friendships = mock(“ActiveRecord collection”, :build =>
@friendship)

   # Issue (1)
   @user = mock_model(User, :friendships => @friendships)

   # Issue (2)
   controller.stub!(:current_user).and_return(@user)
 end

 it "should build a user" do
   # Issue (3)
   @friendships.should_receive(:build).with(:friend_id =>

YOUR_EXPECTED_FRIEND_ID)
# Put whatever rspec-rails provides to invoke the action here
end
end

(disclaimer - not run)

However - this is a fragile solution because it makes assumptions
about the ActiveRecord magic in User (it’s a violation of Law of
Demeter[1], which I believe you have to pay extra attention to with
ActiveRecord code). It is more stable to write a method
User#build_friendship that takes the appropriate args - you can then
spec the behaviour in the model layer.

Sorry I can’t go into more detail about the above issues - I’m writing
this over a quick coffee in the station before I go to the office.

HTH

Ashley

[1] Law of Demeter - Wikipedia


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

Thanks guys, you really helped me a lot where to put my focus on. I will
refactor my code to have it more easily being mocked, I thought about
this at one point, but kept it for readability. Hope to get my specs up
running soon.