7.2. Example for a User in a Web Shop

Let's start with a user scaffold in an imaginary web shop:
$ rails new webshop
  [...]
$ cd webshop
$ rails generate scaffold user login_name first_name last_name birthday:date
      [...]
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      [...]
      invoke    test_unit
      create      test/controllers/users_controller_test.rb
      invoke    helper
      [...]
      invoke      test_unit
      create        test/helpers/users_helper_test.rb
      [...]
$ rake db:migrate
      [...]
$
You already know all about scaffolds (if not, please go and read Chapter 5, Scaffolding and REST first) so you know what the application we have just created does. The scaffold created a few tests (they are easy to recognise because the word test is in the file name).
The complete test suite of a Rails project is processed with the command rake test. Let's have a go and see what a test produces at this stage of development:
$ rake test
Run options: --seed 23117

# Running tests:

.......

Finished tests in 0.696922s, 10.0442 tests/s, 18.6535 assertions/s.

7 tests, 13 assertions, 0 failures, 0 errors, 0 skips
$ 
The output "7 tests, 13 assertions, 0 failures, 0 errors, 0 skips" looks good. By default, a test will run correctly in a standard scaffold.
Let's now edit the app/models/user.rb and insert a few validations (if these are not entirely clear to you, please read Section 4.15, “Validation”):
class User < ActiveRecord::Base
  validates :login_name,
            presence: true,
            length: { minimum: 10 }

  validates :last_name, 
            presence: true
end
Then we execute rake test again:
$ rake test
Run options: --seed 51265

# Running tests:

F.....F

Finished tests in 0.178619s, 39.1896 tests/s, 55.9851 assertions/s.

  1) Failure:
UsersControllerTest#test_should_create_user [/Users/stefan/webshop/test/controllers/users_controller_test.rb:20]:
"User.count" didn't change by 1.
Expected: 3
  Actual: 2

  2) Failure:
UsersControllerTest#test_should_update_user [/Users/stefan/webshop/test/controllers/users_controller_test.rb:39]:
Expected response to be a <redirect>, but was <200>

7 tests, 10 assertions, 2 failures, 0 errors, 0 skips
$
Boom! This time we have "2 failures". The error happens in the "should create user" and the "should update user". The explanation for this is in our validation. The example data created by the scaffold generator went through in the first rake test (without validation). The errors only occurred the second time (with validation).
This example data is created as fixtures in YAML format in the directory test/fixtures/. Let's have a look at the example data for User in the file test/fixtures/users.yml:
one:
  login_name: MyString
  first_name: MyString
  last_name: MyString
  birthday: 2013-07-17

two:
  login_name: MyString
  first_name: MyString
  last_name: MyString
  birthday: 2013-07-17
There are two example records there that do not fulfil the requirements of our validation. The login_name should have a length of at least 10. Let's change the login_name in test/fixtures/users.yml accordingly:
one:
  login_name: MyString12
  first_name: MyString
  last_name: MyString
  birthday: 2013-07-17

two:
  login_name: MyString12
  first_name: MyString
  last_name: MyString
  birthday: 2013-07-17
Now, a rake test completes without any errors again:
$ rake test
Run options: --seed 2058

# Running tests:

.......

Finished tests in 0.150927s, 46.3800 tests/s, 86.1344 assertions/s.

7 tests, 13 assertions, 0 failures, 0 errors, 0 skips
$
We now know that valid data has to be contained in the test/fixtures/users.yml so that the standard test created via scaffold will succeed. But nothing more. We now change the test/fixtures/users.yml to a minimum (for example, we do not need a first_name):
one:
  login_name: MyString12
  last_name: Obama

two:
  login_name: MyString12
  last_name: Bush
To be on the safe side, let's do another rake test after making our changes (you really can't do that often enough):
$ rake test
Run options: --seed 1554

# Running tests:

.......

Finished tests in 0.141682s, 49.4064 tests/s, 91.7548 assertions/s.

7 tests, 13 assertions, 0 failures, 0 errors, 0 skips
$ 

Buy the new Rails 5.1 version of this book.

All fixtures are loaded into the database when a test is started. You need to keep this in mind for your test, especially if you use uniqueness in your validation.

Functional Tests

Let's take a closer look at the point where the original errors occurred:
  1) Failure:
UsersControllerTest#test_should_create_user [/Users/stefan/webshop/test/controllers/users_controller_test.rb:20]:
"User.count" didn't change by 1.
Expected: 3
  Actual: 2

  2) Failure:
UsersControllerTest#test_should_update_user [/Users/stefan/webshop/test/controllers/users_controller_test.rb:39]:
Expected response to be a <redirect>, but was <200>
In the UsersControllerTest the User could not be created nor changed. The controller tests are located in the directory test/functional/. Let's now take a good look at the file test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionController::TestCase
  setup do
    @user = users(:one)
  end

  test "should get index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:users)
  end

  test "should get new" do
    get :new
    assert_response :success
  end

  test "should create user" do
    assert_difference('User.count') do
      post :create, user: { birthday: @user.birthday, first_name: @user.first_name, last_name: @user.last_name, login_name: @user.login_name }
    end

    assert_redirected_to user_path(assigns(:user))
  end

  test "should show user" do
    get :show, id: @user
    assert_response :success
  end

  test "should get edit" do
    get :edit, id: @user
    assert_response :success
  end

  test "should update user" do
    patch :update, id: @user, user: { birthday: @user.birthday, first_name: @user.first_name, last_name: @user.last_name, login_name: @user.login_name }
    assert_redirected_to user_path(assigns(:user))
  end

  test "should destroy user" do
    assert_difference('User.count', -1) do
      delete :destroy, id: @user
    end

    assert_redirected_to users_path
  end
end
At the beginning, we find a setup instruction:
setup do
  @user = users(:one)
end
These three lines of code mean that for the start of each individual test, an instance @user with the data of the item one from the file test/fixtures/users.yml is created. setup is a predefined callback that - if present - is started by Rails before each test. The opposite of setup is teardown. A teardown - if present - is called automatically after each test.

Buy the new Rails 5.1 version of this book.

For every test (in other words, at each run of rake test), a fresh and therefore empty test database is created automatically. This is a different database than the one that you access by default via rails console (that is the development database). The databases are defined in the configuration file config/database.yml. If you want to do debugging, you can access the test database with rails console test.
This functional test then tests various web page functions. First, accessing the index page:
test "should get index" do
  get :index
  assert_response :success
  assert_not_nil assigns(:users)
end
The command get :index accesses the page /users. assert_response :success means that the page was delivered. The line assert_not_nil assigns(:users) ensures that the controller does not pass the instance variable @users to the view with the value nil (setup ensures that there is already an entry in the database).

Buy the new Rails 5.1 version of this book.

The symbol :users is used here to make sure that @users in the controller class to be tested is used, not @users in the test class itself.
Let's look more closely at the two problems from earlier. First, should create user:
test "should create user" do
  assert_difference('User.count') do
    post :create, user: { birthday: @user.birthday, first_name: @user.first_name, last_name: @user.last_name, login_name: @user.login_name }
  end

  assert_redirected_to user_path(assigns(:user))
end
The block assert_difference('User.count') do ... end expects a change by the code contained within it. User.count after should result in +1.
The last line assert_redirected_to user_path(assigns(:user)) checks if after the newly created record the redirection to the corresponding view show occurs.
The second error occurred with should update user:
test "should update user" do
  patch :update, id: @user, user: { birthday: @user.birthday, first_name: @user.first_name, last_name: @user.last_name, login_name: @user.login_name }
  assert_redirected_to user_path(assigns(:user))
end
Here, the record with the id of the @user record was supposed to be updated with the attributes of the @user record. Then, the show view for this record was again supposed to be displayed. Logically, this test could not work either, because a) the @user record did not exist in the database and b) it could not be updated as it was not valid.
Without commenting each individual functional test line by line, it is becoming clear what these tests do: they execute real queries to the Web interface (or actually to the controllers) and so they can be used for testing the controllers.

Buy the new Rails 5.1 version of this book.

With rake test:functionals you can also run just the functional tests in the directory test/functional/.
$ rake test:functionals
Run options: --seed 59879

# Running tests:

.......

Finished tests in 0.152887s, 45.7854 tests/s, 85.0301 assertions/s.

7 tests, 13 assertions, 0 failures, 0 errors, 0 skips
$ 

Unit Tests

For testing the validations that we have entered in app/models/user.rb, units tests are more suitable. Unlike the functional tests, these test only the model, not the controller's work.

Buy the new Rails 5.1 version of this book.

With rake test, all tests present in the Rails project are executed. With rake test:units, only the unit tests in the directory test/models/ are executed.
The unit tests are located in the directory test/models/. But a look into the file test/models/user_test.rb is rather sobering:
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end
By default, scaffold only writes a commented-out dummy test. That is why rake test:units runs through without any content:
$ rake test:units
Run options: --seed 30150

# Running tests:



Finished tests in 0.002333s, 0.0000 tests/s, 0.0000 assertions/s.

0 tests, 0 assertions, 0 failures, 0 errors, 0 skips
$
A unit test always consists of the following structure:
test "an assertion" do
  assert something_is_true_or_false
end
The word assert already indicates that we are dealing with an assertion in this context. If this assertion is true, the test will complete and all is well. If this assertion is false, the test fails and we have an error in the program (you can specify the output of the error as string at the end of the assert line).

Buy the new Rails 5.1 version of this book.

If you have a look around at http://guides.rubyonrails.org/testing.html then you will see that there are some other assert variations. Here are a few examples:
  • assert( boolean, [msg] )
  • assert_equal( obj1, obj2, [msg] )
  • assert_not_equal( obj1, obj2, [msg] )
  • assert_same( obj1, obj2, [msg] )
  • assert_not_same( obj1, obj2, [msg] )
  • assert_nil( obj, [msg] )
  • assert_not_nil( obj, [msg] )
  • assert_match( regexp, string, [msg] )
  • assert_no_match( regexp, string, [msg] )
Let's breathe some life into the first test in the test/unit/user_test.rb:
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  test 'a user with no attributes is not valid' do
    user = User.new
    assert !user.save, 'Saved a user with no attributes.'                       
  end
end
This test checks if a newly created User that does not contain any data is valid (it should not). As assert only reacts to true, I placed a"!" before User.new.valid? to turn the false into a true, as an empty user cannot be valid.
So a rake test:units then completes immediately:
$ rake test:units
Run options: --seed 43622

# Running tests:

.

Finished tests in 0.051971s, 19.2415 tests/s, 19.2415 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
$
Now we integrate two asserts in a test to check if the two fixture entries in the test/fixtures/users.yml are really valid:
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  test 'an empty user is not valid' do
    assert !User.new.valid?, 'Saved an empty user.'
  end

  test "the two fixture users are valid" do
    assert User.new(last_name: users(:one).last_name, login_name: users(:one).login_name ).valid?, 'First fixture is not valid.'
    assert User.new(last_name: users(:two).last_name, login_name: users(:two).login_name ).valid?, 'Second fixture is not valid.'
  end
end
Then once more a rake test:units:
$ rake test:units
Run options: --seed 10228

# Running tests:

..

Finished tests in 0.054056s, 36.9987 tests/s, 55.4980 assertions/s.

2 tests, 3 assertions, 0 failures, 0 errors, 0 skips
$

Thank you for your support and the visibility by linking to this website on Twitter and Facebook. That helps a lot!