Rails Unit Testing with MiniTest for Beginners

Published: 2014-09- 3

When I first started with unit testing in Rails, I found the concept relatively straight forward. All you are doing is checking that a model does this or that. The challenge I had was in figuring out how to express that within the testing framework.

Here is what I have learned that will hopefully help you when you are writing your tests.

First make sure you have some form of automated testing setup as per my previous article. This will allow your tests to automatically run whenever you modify your test or model file. This will save a lot of time. If you have notifications setup, even better, you can just wait for the alert to pop up.

NOTE: The following versions were used for this setup:

  • Rails: 4.1.5
  • Ruby 2.1.2
  • MiniTest 5.4.0

Assertions

Most tests will revolve around doing something with the model and then testing that it returns the expected value, whether it be true, false, nil, a string etc.

In MiniTest this is done with assertions that will test the object by assert something about it. Take this example of a Post model test.

post = Post.create(title: 'Post title')
# post should be valid
assert post.valid?
Here we are creating a post object and checking if it is valid to ensure that it got created.

We can do it the other way around, what if we want to test our validations are working and that the post doesn't save if it is missing the body.

post = Post.create(title: 'Post title')
# post should not be valid as it is missing the body
assert_not post.valid?
# this also means the same thing
assert !post.valid?
Here we are creating a post object that won't save as it is missing the body. There are 2 different ways to check this, I prefer the first assert_not style.

We can also pass a message along with the test that will be displayed when the test fails, this can be helpful in providing more information about why the test failed. It also removes the need for comments as the explanation is right there.

  post = Post.create(title: 'Post title', body: 'Test')
  assert post.valid?, 'The post was not valid when all parameters were supplied'
  post = Post.create(title: 'Post title')
  # post should not be valid as it is missing the body
  assert_not post.valid?, 'The post should not be valid when missing body'
Here we are creating a post object that won't save as it is missing the body. There are 2 different ways to check this, I prefer the first assert_not style.

A list of Assertions

Rails has quite a few assertions, though here is what I find myself using on a regular basis.
With these few assertions, it is easy to test what your models are generating.

assert( test, [msg] ) Ensures that test is true.

assert post.valid?, 'The post was not valid when all parameters were supplied'

assert_not( test, [msg] ) Ensures that test is false

assert_not post.valid?, 'The post should not be valid when missing body'

assert_equal( expected, actual, [msg] ) Ensures that expected == actual is true.

assert_equal 'Post title', post.title, "Post title didn't match"

assert_not_nil (test, message = nil) Ensures that obj.nil? is false.

assert_not_nil post.title, 'Post title should not be nil'
post = Post.find(1)
assert_not_nil post, "Post wasn't found"

Fixtures - your test data

Rails uses fixtures for your sample data for testing. This way you can be assured of what is in the database when you test.

Fixtures are stored as yaml files and are a way of representing your database data in an easy to read format. Here is an example:

test/fixtures/users.yml

# users
admin:
  name: Steve
  email: admin.user@buildingrails.com
  admin: true

regular:
  name: Dave
  email: regular.user@buildingrails.com
  admin: false
Note: There is no id entry in the file, though it is automatically generated in the database.

Rails allows you to easily reference other objects in your yaml files. Here is an example of a Post model that belongs to a User model

model files

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :posts
end
# app/models/post.rb
class Post < ActiveRecord::Base
  validates_presence_of :title, :body
  belongs_to :user
end

fixture files

# test/fixtures/users.yml
admin:
  name: Steve
  email: admin.user@buildingrails.com
  admin: true

regular:
  name: Dave
  email: regular.user@buildingrails.com
  admin: false
# test/fixtures/posts.yml
one
  user: admin
  title: Post Title
  body: This is the body
two
  user: admin
  title: 2nd Post Title
  body: This is the body
Note: Notice how the posts user entry IS NOT user_id, but user, this tells Rails to look for the model reference and then refer to the admin user.

Taking Fixtures to the next level

Rails allows you to embed ERB code directly into your fixtures to allow you to even further customize your data.
You can easily set the date field for your posts to be within a certain time.

# test/fixtures/posts.yml
one
  user: admin
  title: Post Title
  body: This is the body
  created_at: <%= Time.now - 1.weeks %>
two
  user: admin
  title: 2nd Post Title
  body: This is the body
  created_at: <%= Time.now - 2.weeks %>

You can even use a loop to automatically generate a lot of data. You can generate 100 comments for your post without having to create 100 fixture entries.

# test/fixtures/comments.yml
<% 100.times do |n| %>
comment_<%= n %>:
  post: one
  email: <%= "user_#{n}@buildingrails.com" %>
  body: this is comment number <%= n %>
<% end %>

Access fixtures in your tests

Fixtures can be loaded into object variables so you can quickly access an object with known details.

  user = users(:admin)
  post = posts(:two)

What to test?

While it is ideal to test everything, generally this isn't possible, so I try to focus on testing the end result of my models.

  • I always test my validations as these are important to the health of your application, you don't want an accidental deletion or typo to ruin your application.
  • If you get an error in your application, before fixing it in the code, first write a test (that will fail) and then when you fix it, the test will pass.
  • Test any callbacks such as before_create to ensure that your data is being generated as you expected.
  • Test any third party Gems that your models rely on. You never know when a new version of a Gem will do things differently or break your application.

Putting it all together, your first test

Rails will generate a default test for any Models or Scaffolds you generate, here is an example:

# test/models/post.rb
require 'test_helper'

class PostTest < ActiveSupport::TestCase
  # test "Post saves" do
  #  assert true
  # end
end

Within each test block, the database will keep any changes that a test makes so you can create a new object and reference it within that block.

Once a block is complete the database is rolled back to 'normal' and the next test starts fresh with just the fixtures.

# test/models/post_test.rb
require 'test_helper'

class PostTest < ActiveSupport::TestCase
  test "Post saves with all parameters" do
    post = Post.create(title: 'Post title', body: 'body')
    assert post.valid?, 'Post did not save'
  end
end

Now we can put all the tests together into their own blocks

# test/models/post_test.rb
require 'test_helper'

class PostTest < ActiveSupport::TestCase
  test "Post saves with all parameters" do
    post = Post.create(title: 'Post title', body: 'Test')
    assert post.valid?, 'The post was not valid when all parameters were supplied' 
    assert_equal 'Post title', post.title, 'Post title does not match'
  end

  test "Post doesn't save without all parameters" do
    post = Post.create(title: 'Post title')
    assert_not post.valid?, 'The post should not be valid when missing body'
  end
end

Your own tests

Now its time to go and write some of your own tests. As you can see it is easy to get started, and you can slowly build up your test suite over time.

What next?

The next article in the series is: Rails Functional Testing Controllers for Beginners - Part 1

Resources

RailsGuides for Testing - The Rails Guides guide to testing applications.

Rails Automated Testing Setup for Beginners - The previous article on how to setup automated testing in Rails.



comments powered by Disqus