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/.