Rails Functional Testing Controllers for Beginners - Part 1

Published: 2014-09-10

When I first started with functional controller testing in Rails, I struggled till I realised that like unit tests you are just checking that certain events are happening and that certain tags are being placed on the page.

Here is what I have learned that will hopefully help you when you are writing your functional tests.

This article is just going to go over the standard scaffold tests for a controller to cover the basics. Next week I will dive deeper into controller tests.

First make sure you have some form of automated testing setup as per my previous article. This will allow your tests to automatically run whenever you save your files. This will save you a lot of time. If you have notifications setup, even better, you can just wait for the alert to pop up.

Second make sure you have your fixtures setup as per my article: Rails Unit Testing with MiniTest for Beginners .

I will assume that you have read the articles or are at least familiar with fixtures and testing concepts.

NOTE: The following versions were used for this article:

  • Rails: 4.1.5
  • Ruby 2.1.2
  • MiniTest 5.4.1

What are functional tests?

Functional tests in Rails, focus on simulating a get/post/patch/delete HTTP request to a controller action and then checking that the correct response or page is rendered and any appropriate changes take place.

Best to start with an example. First generate a controller in Rails with a single action so it will generate a default functional test.

rails generate controller display index

This generates the output:

# create  app/controllers/display_controller.rb
#  route  get 'display/index'
# invoke  erb
# create    app/views/display
# create    app/views/display/index.html.erb
# invoke  test_unit
# create    test/controllers/display_controller_test.rb
# invoke  helper
# create    app/helpers/display_helper.rb
# invoke    test_unit
# create      test/helpers/display_helper_test.rb
# invoke  assets
# invoke    coffee
# create      app/assets/javascripts/display.js.coffee
# invoke    scss
# create      app/assets/stylesheets/display.css.scss

All controller tests are located in a single file. In our example the contents of the controller test file are:

# test/controllers/display_controller_test.rb
require 'test_helper'

class DisplayControllerTest < ActionController::TestCase
  test "should get index" do
    get :index
    assert_response :success
  end

end

Once you strip away the extra bits, you have a simple test that performs a controller action and checks the response.

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

All we are doing is a standard HTTP get of the index action of the controller.

Our actual test is once again an assertion. In this case, we are checking for a specific response of HTTP 200 OK using a shortcut of :success

Working on a resource controller

Now lets generate a resource controller as this is the most typical type of controller you will work with.

rails generate scaffold Post title:string body:text
This is a simple Post model with a title and body field.

This will generate the following functional test file:

# test/controllers/posts_controller_test.rb
require 'test_helper'

class PostsControllerTest < ActionController::TestCase
  setup do
    @post = posts(:one)
  end

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

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

  test "should create post" do
    assert_difference('Post.count') do
      post :create, post: { body: @post.body, title: @post.title }
    end

    assert_redirected_to post_path(assigns(:post))
  end

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

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

  test "should update post" do
    patch :update, id: @post, post: { body: @post.body, title: @post.title }
    assert_redirected_to post_path(assigns(:post))
  end

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

    assert_redirected_to posts_path
  end
end
Don't worry, we are going to break it down test by test.

The Resource controller: Test by Test

The Setup

  setup do
    @post = posts(:one)
  end

This code is run at the start of every test. In this case the @post variable is built from the fixtures file.
This saves you having to place it at the start of every test and helps to keep your tests readable.
This is where you add any other variables that you want available for all your tests.

The Index

The first actual test is a 'get' of the index page.

  test "should get index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:posts)
  end
We assert that the response is a success
Next we check that the variable @posts has been generated by the controller.
Note: assigns(:posts) is just like an @post variable.

If you had any other variables your app required, you would check for them the same way.

Here is the controller code. As you can see it is generating the @posts variable.

  def index
    @posts = Post.all
  end

The New action

Next we test the new action.

  test "should get new" do
    get :new
    assert_response :success
  end
This is the same as the index file. We could even check for the presences of the @post variable created by the controller.

If you wanted to check a specific value such as if a default value was set, you could do the following.

  test "should get new" do
    get :new
    assert_response :success
    assert_equal 'Default Title', assigns(:post).title
  end

# your controller code
# app/controllers/posts_controller.rb
  def new
    @post = Post.new(title: 'Default Title')
  end

Checking the Post

Now we get a bit more funky as we are now expecting an action to change data.

  test "should create post" do
    assert_difference('Post.count') do
      post :create, post: { body: @post.body, title: @post.title }
    end

    assert_redirected_to post_path(assigns(:post))
  end

Here we can see a new block assert_difference('Post.count') that wraps around the action test.
What this does is before and after the action test it runs Post.count and compares the variables.
As our post is to create a new Post object, this should result in a change to Post.count.
We can even check for negative changes or changes greater than 1 by adding an optional value. More on that later in the destroy test section.

Pro Tip: You can actually nest these to test for changes in other objects. You can also test for no change with assert_no_difference

assert_difference('Notification.count') do
  assert_difference('Post.count') do
    # post :create ... 
  end
end

# test there is no change
assert_no_difference('Post.count') do
  # ...
end

Now we just have the post action test. This is similar go the get :index in that we are sending a HTTP POST to the 'create' action of the controller.

Note: Rails uses HTTP post to create an object and HTTP patch to update an object.

The second argument is a hash of the post variables that the controller will use to create the Post object.
In this case the hash is using the @post from our setup to provide the variables. You could even set these to your own strings yourself.

post :create, post: { body: 'Post Body', title: 'Post Title' }

The last line of the test is checking that we are redirected to the show action of the controller. In this case it is specifically the post_path route just like you would use in controllers and views.
As the post_path requires a post id, this is provided by assigns(:post) which is the newly created post object.

assert_redirected_to post_path(assigns(:post))

Get and Edit the post

The next 2 tests are just like the index test in that they check that the page loads for the show and edit actions.

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

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

Update the post

Testing the update action is very similar to the create action in that we use HTTP path to send a hash of values.
This time the post object already exists, so we provide the id of the post to let the controller know which post we want to update.

  test "should update post" do
    patch :update, id: @post, post: { body: @post.body, title: @post.title }
    assert_redirected_to post_path(assigns(:post))
  end

All things must end. The destroy action.

All that is left is to test that the post can be deleted.

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

    assert_redirected_to posts_path
  end

Here we are using assert_difference with a second variable of -1 to check that we are deleting the post.

The HTTP delete to the destroy controller action just needs one variable, the id of the post.

Lastly the post doesn't exist any more, so we check that we are redirected to the index action of the controller using a standard route path again.

What next?

The next Article in the series is: Rails Functional Testing Controllers for Beginners - Part 2 - here I go into some extra tests for your controllers.

Resources

RailsGuides for Functional Testing Controllers - The Rails Guides test section on functional testing controllers.

Rails Automated Testing Setup for Beginners - The previous article on how to setup automated testing in Rails.

Rails Unit Testing with MiniTest for Beginners - The previous article on how to setup unit testing and fixtures in Rails.



comments powered by Disqus