How we want our test environment
- Isolation of input fixtures, so that changing fixture of one test code will not impact others.
- Easily create input set and reuse existing.
- Readable test code.
- Runs very fast(Slower test codes are run less by developers).
- Write less to test.
Problems with built in fixture and test helper
- Fixture does not allow to isolate input set.
- No feature of reusability in fixtures causes developer to create unnecessary duplication in fixtures.
- Changes in table definition causes a lot of change in fixture set.
- Fixtures are hidden in different file. So it becomes difficult to trace a test code.
- Fixture is database driven. So even if you do not need a saved instance of an ActiveRecord object, you end up using one. The more the queries get executed, the slower the test code becomes.
Factory girl, one of the alternate of fixture seems to be the better solution then fixture. And shoulda makes the life easier for writing test codes.
Why factory?
- Isolated input set
- Different build method gives the flexibility to create saved and unsaved instances
- Can easily extend existing factory to create a new one
- Facility to override property and to stub methods to reduce database query
- Increase readability of test codes.
Why Shoulda?
- Context & Should blocks - Context and should block provides facilities to do BDD
- Assertions - Provides many common and useful assertions
- Macros - Generate many ActionController and ActiveRecord tests with helpful error messages
So let’s get some action. Here is a simple test done using factory and shoulda. We will write unit test for User model which looks like the following.
class User < ActiveRecord::Base has_many :posts, :order => 'created_at DESC' has_many :comments validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i validates_uniqueness_of :email def display_name return "#{first_name} #{last_name}" end def latest_post return self.posts.first end end
The Post model is,
class Post < ActiveRecord::Base belongs_to :user has_many :comments, :dependent => :destroy validates_presence_of :title validates_presence_of :body end
And the user factory is,
Factory.sequence :email do |n| "email#{n}@test.com" end Factory.define :user do |u| u.first_name 'Ashraf' u.last_name 'Zaman' #allows you to create different users with different email using the same definition u.email { Factory.next(:email) } end
Post factory is,
Factory.define :post do |p| p.title 'A sample post' p.body 'This is a sample post' p.created_at Time.now p.updated_at Time.now end
Finally the user model test is,
require 'test_helper' class UserTest < ActiveSupport::TestCase should_have_many :posts should_have_many :comments should_require_unique_attributes :email should_not_allow_values_for :email, "ab.cd", "1234@" should_allow_values_for :email, "ashrafuzzaman.g2@gmail.com" should 'display_name' do user = Factory.build(:user, :first_name => 'Ashraf', :last_name => 'Zaman') assert_equal('Ashraf Zaman', user.display_name) end context 'Default posts' do setup do @user = Factory.build(:user, :posts => [ Factory.build(:post, :title => 'Second Post', :created_at => Time.now), Factory.build(:post, :title => 'First Post', :created_at => 2.minutes.ago) ]) end should 'return latest post' do assert_equal('Second Post', @user.latest_post.title) end end end
You can download the demo from here
How factory helped
To test the display_name I do not need a saved instance, So I used Factory.build to build the model object with out saving it in the database.
Note: With Factory.build you can create an unsaved model instance from the factory prototype. It is very useful for testing model logic.
Some attributes of user prototype are intentionally redefined to increase the readability of the test code. Now you can easily read the test code as you can see the input set right in front of you. Thus you can effortlessly create different input set from one basic prototype.
The interesting part of this section is the test of latest_post of user model. Here as you can see the association method ‘posts’ is overridden with expected post list. Now you can mock this this methods as this is provided by the framework itself. So no need to test it. But by mocking this you actually increased the readability of the test code and avoided database call.
How shoulda helped
The Shoulda gem makes it easy to write elegant, understandable, and maintainable Ruby tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework.
With shoulda we defined a context 'Default posts' and initialized it. We can test codes related to posts with in the context. As the context is setup with the minimum and sufficient information of input set needed for the context, the test code becomes very simple and easy to read and maintain.
Resources
No comments:
Post a Comment