Learn Ruby on Rails Book

Testing User model

Why write tests

Testing is an important aspect of software development life cycle. When our application grows in size, we are likely to refactor a lot of code. Writing tests helps ensure that changes made to the code do not break the desired functionality.

Tests also help us understand the functionalities of the project. For example, let's say that there were 3 developers working on a project. All of a sudden due to urgent requirements, a developer had to move into another project and a new developer replaced that position.

Then one of the easiest and thorough ways of understanding the codebase, is by reading the test cases corresponding to each entity.

Rails provides a very easy way of writing tests.

Test setup

When a new Rails application is created using rails new command, Rails automatically creates a directory called test.

The following is an example of the structure of the test directory:

1$ brew install tree
2$ cd test
3$ tree
5├── carriers
6├── channels
7│   └── application_cable
8│       └── connection_test.rb
9├── controllers
10│   └── tasks_controller_test.rb
11├── helpers
12├── integration
13├── mailers
14├── models
15├── system
16├── services
17├── workers
18└── test_helper.rb

Most of the directory names suggest what kind of tests it can contain.

Integration tests are responsible to test interactions between controllers while system tests are responsible to test the app as per user's experience on a browser and includes testing JavaScript code.

In this book we won't be covering system tests. Rather we give more precedence to model, controller, worker, carrier, and service tests.

The test_helper.rb file holds the configuration for all the tests. This file will be loaded in all the test files in order to load the test configurations.

Bringing translation helpers into test cases

ActionView::Helpers::TranslationHelper is a Rails module which provides various helper methods which we are going to use in our tests.

Helpers like t() aren't available by default. We need to explicitly include them in our test suite.

Open "test/test_helper.rb" and add the following line.

1class ActiveSupport::TestCase
2  include ActionView::Helpers::TranslationHelper
4  # Run tests in parallel with specified workers
5  parallelize(workers: :number_of_processors)
7  # Add more helper methods to be used by all tests here...

The setup method

Every test runs independently of each other. The data we create in one of the tests won't be affecting the next one. So we will have to create new data every time we run a new test.

This is where the setup method comes in handy. Rails will run this method automatically before every test. So, we can use this method to run common tasks like setting up data, loading configuration data, etc.

Create a new file test/model/user_test.rb and populate it with the following content:

1require 'test_helper'
3class UserTest < ActiveSupport::TestCase
4  def setup
5    @user = User.new(name: "Sam Smith",
6                     email: "sam@example.com",
7                     password: "welcome",
8                     password_confirmation: "welcome")
9  end
11  # embed new test cases here...

Now, let's add the tests.

Adding tests for User model

First, we can add a validation test for presence of name when creating a user:

1def test_user_should_be_not_be_valid_without_name
2  @user.name = ''
3  assert_not @user.valid?
4  assert_equal ["Name can't be blank"], @user.errors.full_messages

We can also write a test to validate length of the value assigned to name field:

1def test_name_should_be_of_valid_length
2  @user.name = "a" * 50
3  assert @user.invalid?

Let's add validations ensuring that the email field works as intended: :

1def test_user_should_be_not_be_valid_and_saved_without_email
2  @user.email = ""
3  assert_not @user.valid?
5  @user.save
6  assert_equal ["Email can't be blank", "Email is invalid"],
7                 @user.errors.full_messages
10def test_user_should_not_be_valid_and_saved_if_email_not_unique
11  @user.save!
13  test_user = @user.dup
14  assert_not test_user.valid?
16  assert_equal ["Email has already been taken"],
17                 test_user.errors.full_messages
20def test_reject_email_of_invalid_length
21  @user.email = ("a" * 50) + "@test.com"
22  assert @user.invalid?
25def test_validation_should_accept_valid_addresses
26  valid_emails = %w[user@example.com USER@example.COM US-ER@example.org
27                      first.last@example.in user+one@example.ac.in]
29  valid_emails.each do |email|
30    @user.email = email
31    assert @user.valid?
32  end
35def test_validation_should_reject_invalid_addresses
36  invalid_emails = %w[user@example,com user_at_example.org user.name@example.
37                        @sam-sam.com sam@sam+exam.com fishy+#.com]
39  invalid_emails.each do |email|
40    @user.email = email
41    assert @user.invalid?
42  end

We have also written tests to validate our email uniqueness,length and format when creating a new user. Here email has to be unique since we use it to uniquely identify a user.

Testing valid email format with multiple valid and invalid emails helps us to cover the edge cases. It's always a good practice to test with multiple values.

Now let's add some more tests to validate password field:

1def test_user_should_not_be_saved_without_password
2  @user.password = nil
3  assert_not @user.save
4  assert_equal ["Password can't be blank"],
5                 @user.errors.full_messages
8def test_user_should_match_password_and_password_confirmation
9  @user.password_confirmation = ''
10  assert_not @user.save
11  assert_equal ["Password confirmation doesn't match Password"],
12                 @user.errors.full_messages
15def test_users_should_have_unique_auth_token
16  @user.save!
17  second_user = User.create!(name: "Olive Sans", email: "olive@example.com",
18                               password: "welcome", password_confirmation: "welcome")
20  assert_not_same @user.authentication_token,
21                    second_user.authentication_token

First test case validates that the password field can't be blank. Here we've tested that password & password_confirmation has to be the same while creating a user.

The unique authentication_token is stored for every user while creating a new user. Last test case validates that a newly created user must have a unique authentication_token.

Finally, to run these tests, execute the command:

1bundle exec rails test test/models/user_test.rb

Add a -v flag if you want to the output with higher verbosity:

1bundle exec rails test -v test/models/user_test.rb

Now let's commit these changes:

1git add -A
2git commit -m "Added tests for User model"
    to navigateEnterto select Escto close