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.
The post Useful helpers for RSpec mocks first appeared on Dmytro Shteflyuk's Home.]]>
For the beginning we would define helper for reading fixtures and put them into the spec/mailer_spec_helper.rb file:
1 2 3 4 5 6 7 8 9 | require File.dirname(__FILE__) + '/spec_helper.rb' module MailerSpecHelper private def read_fixture(action) IO.readlines("#{FIXTURES_PATH}/mailers/user_mailer/#{action}") end end |
Now we need to create fixtures for mailers in the spec/fixtures/mailers folder, each mailer in the separate subfolder like spec/fixtures/mailers/some_mailer/activation:
1 | Hello, Bob |
In spec/models/mailers/some_mailer_spec.rb we would write something like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | require File.dirname(__FILE__) + '/../../mailer_spec_helper.rb' context 'The SomeMailer mailer' do FIXTURES_PATH = File.dirname(__FILE__) + '/../../fixtures' CHARSET = 'utf-8' fixtures :users include MailerSpecHelper include ActionMailer::Quoting setup do # You don't need these lines while you are using create_ instead of deliver_ #ActionMailer::Base.delivery_method = :test #ActionMailer::Base.perform_deliveries = true #ActionMailer::Base.deliveries = [] @expected = TMail::Mail.new @expected.set_content_type 'text', 'plain', { 'charset' => CHARSET } @expected.mime_version = '1.0' end specify 'should send activation email' do @expected.subject = 'Account activation' @expected.body = read_fixture('activation') @expected.from = '[email protected]' @expected.to = users(:bob).email Mailers::UserMailer.create_activation(users(:bob)).encoded.should == @expected.encoded end end |
That’s almost all. We are fully confident that emails look as we expected. In controller we don’t need to re-test mailers again, we just need to become convinced of mailer calling!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | context 'Given an signup action of UserController' do controller_name 'user' setup do @user = mock('user') @valid_user_params = { :email => '[email protected]' } end specify 'should deliver activation email to newly created user' do User.should_receive(:new).with(@valid_user_params).and_return(@user) Mailers::UserMailer.should_receive(:deliver_activation).with(@user) post :signup, :user => @valid_user_params response.should redirect_to(user_activate_url) end end |
I stopped getting confused with order of expected and real values. Compare:
1 | assert('login', @user.login) |
and
1 | @user.login.should == 'login' |
BTW, I made a mistake in the assert_equal parameters order when was writing this article.
I could understand what objects should do at first look on the test:
1 2 3 4 5 6 7 | context 'The User model with fixtures loaded' do specify 'should be friended by the user if this user added him to friends' do users(:bob).friends << users(:chris) users(:chris).should have(1).friended users(:chris).friended.first.should == users(:bob) end end |
Test results are presented in easy to understand and clear form, just simple sentences, which are making asserts about entities in the system. In most cases there is even no necessity to look at the tests to understand which functionality has one or another entity!
1 2 3 4 5 6 7 8 9 | The newly created User - should be valid with all neccessary attributes specified - should be invalid without login - should be invalid when login length is less than 2 - should be invalid without email - should be invalid with wrong email - should be invalid with someone else's login - should be invalid with someone else's email - should not require password and password_confirmation |
I started writing tests before actual coding finally! In fact, every developer think over what one or another method should do, and frequently these thoughts looks like “when method received string ‘somestring’ it should return ‘anotherstring'”. It’s easy to describe these thoughts using BDD-tests.
Integrated flexible mock-objects system allows testing functionality just once. For example, when you test controllers, you should not use database and models – it is sufficient to check, if necessary model methods would be called in right order with right parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | context 'Given an signup action of UserController' do controller_name 'user' setup do @user = mock('user') add_stubs(@user, :password= => nil, :password_confirmation= => nil, :new_password= => nil) end specify 'should render edit template with empty User object' do User.should_receive(:new).with(:no_args).and_return(@user) get :signup response.should render_template(:edit) assigns[:user].should be(@user) end end |
To install gem enter command:
1 | gem install fixturease |
It’s time to try something:
1 2 3 4 5 6 7 8 9 10 11 12 | # Loading Fixturease console for test mode. @mike = User.create(:login => 'mike', :password => 'pass') # <User:0x482ac18 @new_record_before_save=true, @attributes={"id"=>5, "password"=>"pass", "login"=>"mike", "created_at"=>Sat Jan 20 21:40:23 +0200 2007}, @errors=#<ActiveRecord::Errors:0x4827b08 @base=#<User:0x482ac18 ...>, @errors={}>, @new_record=false> @mike_post_1 = @mike.posts.create(:name => 'first post by mike') # <Post:0x4811bb4 @new_record_before_save=true, @attributes={"name"=>"first post by mike", "updated_at"=>Sat Jan 20 21:40:38 +0200 2007, "id"=>3, "user_id"=>5, "created_at"=>Sat Jan 20 21:40:38 +0200 2007}, @errors=#<ActiveRecord::Errors:0x4811510 @base=#<Post:0x4811bb4 ...>, @errors={}>, @new_record=false> @mike_post_2 = @mike.posts.create(:name => 'second post by mike') # <Post:0x48099dc @new_record_before_save=true, @attributes={"name"=>"second post by mike", "updated_at"=>Sat Jan 20 21:40:45 +0200 2007, "id"=>4, "user_id"=>5, "created_at"=>Sat Jan 20 21:40:45 +0200 2007}, @errors=#<ActiveRecord::Errors:0x48093d8 @base=#<Post:0x48099dc ...>, @errors={}>, @new_record=false> exit # Saving fixtures: # User [./spec/fixtures/users.yml]: mike # Post [./spec/fixtures/posts.yml]: mike_post_1 and mike_post_2 |
In this very simple case we just created one fixture for User model named mike and two for Post model named mike_post_1 and mike_post_2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | mike: id: 5 password: pass login: mike created_at: 2007-01-20 21:40:23.250000 +02:00 mike_post_1: name: first post by mike updated_at: 2007-01-20 21:40:38.765000 +02:00 id: 3 user_id: 5 created_at: 2007-01-20 21:40:38.765000 +02:00 mike_post_2: name: second post by mike updated_at: 2007-01-20 21:40:45.562000 +02:00 id: 4 user_id: 5 created_at: 2007-01-20 21:40:45.562000 +02:00 |
You can always load fixtures to current environment using command load_fixtures! (or command-line switch -l). It allows you to use previously created fixtures to build new ones.
I like unit tests but I hate to write fixtures. This nice gem allows me to relax and to get pleasure from work.
The post Create your fixtures easily first appeared on Dmytro Shteflyuk's Home.]]>