Rails 4.2 Tutorial 3 - Test

  • Mar 1

Testing is an essential part of application development. We will use built-in test suite for this purpose.

To make test run faster, we can use spring. It is already in the Gemfile. We just need to set it up:

$ bundle exec spring binstub --all

It will insert a small code in bin/rails and bin/rake.

Since we use devise and many tests need to run as user signed in, we need devise test helper:

# test/test_helper.rb 
class ActionController::TestCase
  include Devise::TestHelpers
end

We have user model only for now. This is the fixture as data for testing:

# test/fixtures/users.yml 
user_one: 
  username: user_one
  email: one@gmail.com
  encrypted_password: <%= User.new.send(:password_digest, "user_one") %>
user_two:
  username: user_two
  email: two@gmail.com
  encrypted_password: <%= User.new.send(:password_digest, "user_two") %>

Then we can start an easy test to see whether everything works:

$ bin/rake test test/controllers/pages_controller_test.rb

It should pass nicely. For user controller, we use these default tests:

class UsersControllerTest < ActionController::TestCase
  setup do
    @user = users(:user_one)
    sign_in @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: { username: 'xxx' }
    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 root_path
  end
end

It will not pass because for destroy, we need to redirect to root path. Modify the user controller:

# app/controllers/users_controller.rb
  def destroy                                                                                                    
    @user.destroy                                                                                                
    respond_to do |format|                                                                                       
      format.html { redirect_to root_url, notice: 'User was successfully destroyed.' }                           
      format.json { head :no_content }                                                                           
    end                                                                                                          
  end  

It should pass all tests.

From now, a lot of testing can be written. For example, an user can only delete own account:

# app/controllers/users_controller.rb
  def destroy
    msg = 'User was successfully destroyed.'
    if user_signed_in? && @user == current_user
      @user.destroy
    else
      msg = 'User cannot be destroyed'
    end

    respond_to do |format|
      format.html { redirect_to root_url, notice: msg }
      format.json { head :no_content }
    end
  end    
# test/controllers/users_controller_test.rb
  test "should destroy user" do
    assert_difference('User.count', -1) do
      delete :destroy, id: @user
    end

    assert_redirected_to root_path
  end

  test "should not destroy user by others" do
    @user_two = users(:user_two)
    assert_no_difference('User.count') do
      delete :destroy, id: @user_two
    end

    assert_redirected_to root_path
  end

  test "should not destroy user if not signed in" do
    sign_out @user
    assert_no_difference('User.count') do
      delete :destroy, id: @user
    end

    assert_redirected_to root_path
  end   

Good tests help you avoid corner cases. There are many choices of assertions to use. I suggest to write tests for model and controller as early as possible. For views, wait until the application matures. Otherwise, you will spend a lot of time adjusting both application and test suites.