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.
When a new Rails application is created using
rails new command, Rails
automatically creates a directory called
The following is an example of the structure of the
1$ brew install tree 2$ cd test 3$ tree 4. 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.
In this book we won't be covering system tests. Rather we give more precedence
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.
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 3 4 # Run tests in parallel with specified workers 5 parallelize(workers: :number_of_processors) 6 7 # Add more helper methods to be used by all tests here... 8end
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
1require 'test_helper' 2 3class UserTest < ActiveSupport::TestCase 4 def setup 5 @user = User.new(name: "Sam Smith", 6 email: "firstname.lastname@example.org", 7 password: "welcome", 8 password_confirmation: "welcome") 9 end 10 11 # embed new test cases here... 12end
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 5end
We can also write a test to validate length of the value assigned to
1def test_name_should_be_of_valid_length 2 @user.name = "a" * 50 3 assert @user.invalid? 4end
Let's add validations ensuring that the
1def test_user_should_be_not_be_valid_and_saved_without_email 2 @user.email = "" 3 assert_not @user.valid? 4 5 @user.save 6 assert_equal ["Email can't be blank", "Email is invalid"], 7 @user.errors.full_messages 8end 9 10def test_user_should_not_be_valid_and_saved_if_email_not_unique 11 @user.save! 12 13 test_user = @user.dup 14 assert_not test_user.valid? 15 16 assert_equal ["Email has already been taken"], 17 test_user.errors.full_messages 18end 19 20def test_reject_email_of_invalid_length 21 @user.email = ("a" * 50) + "@test.com" 22 assert @user.invalid? 23end 24 25def test_validation_should_accept_valid_addresses 26 valid_emails = %w[email@example.com USER@example.COM US-ER@example.org 27 firstname.lastname@example.org email@example.com] 28 29 valid_emails.each do |email| 30 @user.email = email 31 assert @user.valid? 32 end 33end 34 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] 38 39 invalid_emails.each do |email| 40 @user.email = email 41 assert @user.invalid? 42 end 43end
We have also written tests to validate our
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
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 6end 7 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 13end 14 15def test_users_should_have_unique_auth_token 16 @user.save! 17 second_user = User.create!(name: "Olive Sans", email: "firstname.lastname@example.org", 18 password: "welcome", password_confirmation: "welcome") 19 20 assert_not_same @user.authentication_token, 21 second_user.authentication_token 22end
First test case validates that the
password field can't be blank. Here we've
password_confirmation has to be the same while
creating a user.
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
Finally, to run these tests, execute the command:
1bundle exec rails test test/models/user_test.rb
-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"