В 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). Первым параметром метод принимает класс модели, вторым — хэш заглушек, но это я уже говорил. Вот в принципе и все.

поправь пример:
2
3
4
5
6
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
Спасибо, провтыкал :-)
А как быть с проверкой модели на валидность? Я попробовал такой код:
2
3
4
@comment = mock_model(Comment, :user => nil)
@comment.should_not be_valid
end
и получил ответ, что сообщение :valid? неизвестно объекту @comment.
Нужно еще добавить строчку
Либо передать ее в mock_model
Когда используется mock_model, создается не полноценная модель, а только ее “тень”. Потому специфические методы нужно стабить руками.
Спасибо, полезная статья :) Я как раз пытаюсь изучать rspec =)