Christoph ----
2009-Oct-21 05:40 UTC
[rspec-users] 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
--
Posted via http://www.ruby-forum.com/.
On 21 Oct 2009, at 06:40, Christoph ---- wrote:> 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.friendshipHi 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] http://en.wikipedia.org/wiki/Law_of_Demeter -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran http://aviewfromafar.net/
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 Tue, Oct 20, 2009 at 10:40 PM, Christoph ---- <lists at ruby-forum.com>
wrote:> 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
> --
> Posted via http://www.ruby-forum.com/.
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>
Christoph ----
2009-Oct-22 06:16 UTC
[rspec-users] how to mock active record relationships
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. -- Posted via http://www.ruby-forum.com/.