Удобные хелперы для мок-объектов RSpec

Posted by Dmytro Shteflyuk on under Ruby & Rails · English (24,775 views)

В RSpec есть такая замечательная штука, как мок-объекты (mocks). В двух словах: мок-объект имитирует поведение объекта, который используется тестируемыми методами. Сразу простой пример:

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

Итак, что же тут написано. Сначала мы создаем мок-объект @user, который будет выступать в роли реальной модели. Только не надо меня спрашивать, зачем такие сложности и почему нельзя просто использовать реальную модель. Я сам расскажу. Во-первых, моки намного быстрее, чем работа с базой — при большом количестве тестов (а у вас ведь их много, правда?) общее время выполнения тестов будет в несколько раз меньше при работе с моками. Во-вторых, вы ведь не хотите тестировать одни и те же методы по сто раз? В моем примере метод preferred_name имеет нетривиальную логику (это не просто поле из базы), и он уже протестирован в спецификации модели. При использовании модели в тестах хелпера, если работа этого метода нарушается, свалятся тесты сразу в двух местах, что не очень удобно. Кроме того, есть такая интересная вещь rcov, которая показывает, какая часть кода покрыта тестами. Так вот если бы тестов модели не было, а в тестах хелпера использовалась модель,– rcov показал бы метод preferred_name покрытым, что не совсем верно. Что-то я отвлекся.

Да, если кто не в курсе, stub (заглушка) — это метод, который ничего не делает, а просто возвращает значение, какое вы ему укажете.

Итак, у нас есть тест. Может его можно как-то упростить? Да!

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

Уже немного приятнее. Вспомогательный метод add_stubs добавляет к объекту, указанному первым параметром, заглушки, которые передаются во втором параметре в виде хэша. Но это еще не все. Специально для моделей Active Record в RSpec есть метод mock_model, который автоматически создает несколько заглушек, стандартных для моделей Ruby on Rails, и принимает хэш с заглушками, как add_stubs:

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

Вы наверняка обратили внимание, что я опустил параметры :id и :new_record?, и сделал это не случайно. Во-первых, этот метод автоматически раздает уникальные idы моделям, которые создаются с его помощью, во-вторых — определяет методы to_param (возвращает строковое представление id) и new_record? (возвращает false). Первым параметром метод принимает класс модели, вторым — хэш заглушек, но это я уже говорил. Вот в принципе и все.

5 Responses to this entry

Subscribe to comments with RSS

liquidautumn @
said on Май 6, 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 Май 6, 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 Май 6, 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 Ноябрь 4, 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.