Search
⌘K
    to navigateEnterto select Escto close

    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
    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.

    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
    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/models/user_test.rb and populate it with the following content:

    1require 'test_helper'
    2
    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
    10
    11  # embed new test cases here...
    12end

    Now, let's add the tests.

    Adding tests for User model

    All the following test cases are self-explanatory, in the sense, reading the test case name itself will give you an idea about what we are testing.

    Method names for test cases must start with test_ in order for them to be executed while running the tests. It is so because, a test class is like any other Ruby class and it can have methods other than the test cases.

    It doesn't make sense to invoke all methods inside a test class rather only test cases should be executed. Hence to differentiate between test cases and other methods, you should prefix the test cases with test.

    Doing so will allow Rails to run only test cases and skip other methods inside a test class while running the tests.

    The following test cases should be added after the setup method in test/models/user_test.rb.

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

    1def test_user_should_not_be_valid_and_saved_without_name
    2  @user.name = ''
    3  assert_not @user.valid?
    4  assert_includes @user.errors.full_messages, "Name can't be blank"
    5end

    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" * 350
    3  assert @user.invalid?
    4end

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

    1def test_user_should_not_be_valid_and_saved_without_email
    2  @user.email = ""
    3  assert_not @user.valid?
    4
    5  @user.save
    6  assert_includes @user.errors.full_messages, "Email can't be blank", "Email is invalid"
    7end
    8
    9def test_user_should_not_be_valid_and_saved_if_email_not_unique
    10  @user.save!
    11
    12  test_user = @user.dup
    13  assert_not test_user.valid?
    14
    15  assert_includes test_user.errors.full_messages, "Email has already been taken"
    16end
    17
    18def test_reject_email_of_invalid_length
    19  @user.email = ("a" * 350) + "@test.com"
    20  assert @user.invalid?
    21end
    22
    23def test_validation_should_accept_valid_addresses
    24  valid_emails = %w[user@example.com USER@example.COM US-ER@example.org
    25    first.last@example.in user+one@example.ac.in]
    26
    27  valid_emails.each do |email|
    28    @user.email = email
    29    assert @user.valid?
    30  end
    31end
    32
    33def test_validation_should_reject_invalid_addresses
    34  invalid_emails = %w[user@example,com user_at_example.org user.name@example.
    35    @sam-sam.com sam@sam+exam.com fishy+#.com]
    36
    37  invalid_emails.each do |email|
    38    @user.email = email
    39    assert @user.invalid?
    40  end
    41end
    42
    43def test_email_should_be_saved_in_lowercase
    44  uppercase_email = "SAM@EMAIL.COM"
    45  @user.email = uppercase_email
    46  @user.save!
    47  assert_equal uppercase_email.downcase, @user.email
    48end

    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.

    We always need to validate against multiple test data. This assures there are no loopholes in our tests.

    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_includes @user.errors.full_messages, "Password can't be blank"
    5end
    6
    7def test_user_should_not_be_saved_without_password_confirmation
    8  @user.password_confirmation = nil
    9  assert_not @user.save
    10  assert_includes @user.errors.full_messages, "Password confirmation can't be blank"
    11end
    12
    13def test_user_should_have_matching_password_and_password_confirmation
    14  @user.password_confirmation = "#{@user.password}-random"
    15  assert_not @user.save
    16  assert_includes @user.errors.full_messages, "Password confirmation doesn't match Password"
    17end
    18
    19def test_users_should_have_unique_auth_token
    20  @user.save!
    21  second_user = User.create!(name: "Olive Sans", email: "olive@example.com",
    22    password: "welcome", password_confirmation: "welcome")
    23
    24  assert_not_same @user.authentication_token, second_user.authentication_token
    25end

    First two test cases validates that the password and password_confirmation fields can't be blank. Here we've also 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.

    We will be adding more test cases for the User model in Deep diving into unit testing chapter.

    Running tests

    Finally, to run these tests, execute the command:

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

    It's recommended to pass in the -v flag so that the output will have higher verbosity and thus will be more clearer:

    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"
    Previous
    Next