Useful helpers for RSpec mocks

Posted by Dmytro Shteflyuk on under Ruby & Rails

RSpec has great feature — mock objects (mocks). In a few words: mock object imitates behavior of the object, which used by the tested methods. And simple example immediately:

1
2
3
4
5
6
7
8
9
10
describe UserHelper
  it 'should generate correct link to user profile in user_link' do
    @user = mock('User')
    @user.stub!(:id, 10)
    @user.stub!(:new_record?, false)
    @user.stub!(:preferred_name, 'Dmytro S.')
    @user.stub!(:full_name, 'Dmytro Shteflyuk')
    user_link(@user).should == link_to('Dmytro S.', user_url(:id => 10), :title => 'Dmytro Shteflyuk')
  end
end

Well, and what does it mean? Initially we are creating mock object @user, which would be used instead of real model. Please don’t ask me why do we need such complexity and why we can’t just use real model. I will explain it myself. On the one hand, mocks are much faster than the database operations — when you have many tests (and you have, right?) total tests execution time would be much smaller if we would use mock objects. On the other hand, you don’t want to test the same methods again and again, right? In my example preferred_name method has non-trivia logic (it’s not simple database field), and it has already been tested in model spec. Imagine that you are using model in helper tests. If this method would break down, two specifications will be failed instead of one — model specification. In addition, there is one interesting feature exists — rcov, which shows how your code is covered by tests. Thus if model tests would not exist, and helper tests would use model,– rcov will show preferred_name method as covered, but it is not true. Oops, I have been distracted.

Oh yes, if you don’t know, stub is just method, which does nothing, it just return value, which you have passed to it.

So we have a test. Could we simplify it somehow? Yep!

1
2
3
4
5
6
7
describe UserHelper
  it 'should generate correct link to user profile in user_link' do
    @user = mock('User')
    add_stubs(@user, :id => 10, :new_record? => false, :preferred_name => 'Dmytro S.', :full_name => 'Dmytro Shteflyuk')
    user_link(@user).should == link_to('Dmytro S.', user_url(:id => 10), :title => 'Dmytro Shteflyuk')
  end
end

Much better. Helper method add_stubs adds to the object, passed as a first parameter, stubs, passed as a second parameter in hash. But it’s not all. Especially for the Active Record models RSpec has method mock_model, which automatically creates several stubs, common for all Ruby on Rails models, and accepts hash with stubs just like add_stubs does:

1
2
3
4
5
6
describe UserHelper
  it 'should generate correct link to user profile in user_link' do
    @user = mock_model(User, :preferred_name => 'Dmytro S.', :full_name => 'Dmytro Shteflyuk')
    user_link(@user).should == link_to('Dmytro S.', user_url(:id => @user.id), :title => 'Dmytro Shteflyuk')
  end
end

You definitely noticed that I have missed parameters :id and :new_record?, and it was not coincidence. Firstly, mock_model automatically defines unique ids for models, which were created using it. Secondly, it defines methods to_param (returns string representation of the id) and new_record? (returns false). The first parameter of the method is a model class, and the second one is a hash of stubs, as I have already said. That’s all, folks.

5 Responses to this entry

Subscribe to comments with RSS

liquidautumn
said on May 6th, 2007 at 03:17 · Permalink

поправь пример:

1
2
3
4
5
6
describe UserHelper
  it 'should generate correct link to user profile in user_link' do
    @user = mock_model(User, :preferred_name => 'Dmytro S.', :full_name => 'Dmytro Shteflyuk')
    user_link(@user).should == link_to('Dmytro S.', user_url(:id => @user.id), :title => 'Dmytro Shteflyuk')
  end
end
said on May 6th, 2008 at 15:34 · Permalink

А как быть с проверкой модели на валидность? Я попробовал такой код:

1
2
3
4
  it "should be invalid withoud user" do
    @comment = mock_model(Comment, :user => nil)
    @comment.should_not be_valid
  end

и получил ответ, что сообщение :valid? неизвестно объекту @comment.

said on May 6th, 2008 at 15:49 · Permalink

Нужно еще добавить строчку

1
@comment.stub!(:is_valid?, false)

Либо передать ее в mock_model

1
@comment = mock_model(Comment, :user => nil, :is_valid? => false)

Когда используется mock_model, создается не полноценная модель, а только ее “тень”. Потому специфические методы нужно стабить руками.

said on November 4th, 2008 at 17:45 · Permalink

Спасибо, полезная статья :) Я как раз пытаюсь изучать rspec =)

Comments are closed

Comments for this entry are closed for a while. If you have anything to say – use a contact form. Thank you for your patience.