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.
поправь пример:
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 =)