Minitest: Testing Controllers & Requests
Minitest do not come with any feature for testing requests, unlike request spec in RSpec. Fortunately, controller tests are able to test through everything in the backend like request tests if we don't mock anything.
Here's how a controller test works.
A Basic Controller Test
A basic controller test includes a HTTP verb and the response we expect.
require 'minitest/autorun'
class PostsControllerTest < ActionController::TestCase
def setup
@post = Post.create(title: 'Hello World')
end
def test_index
get :index
assert_response 200
assert_includes @post.title, @response.body
end
end
From this case we know there are methods and variables accessible exclusively for controller tests like assert_response
and @response
.
Available Assertions
In controller tests, you got several extra assertions to use:
assert_response :success
assert_response :redirect
assert_response :missing
assert_response :error
assert_redirectd_to "/"
assert_redirectd_to root_path
assert_redirectd_to { controller: 'posts', action: 'index' }
assert_template 'posts/index'
assert_template partial: '_customer', count: 2
Besides to the assertions tailored for controllers, there are instance variables available in the tests:
@controller
@request
@response
I found @response
the most useful among them. It is very useful to inspect the @response
and see if everything goes well. For example, if we're rendering a JSON output, we can expect there are keys and values contained in the body.
assert_includes @response.body, keys
assert_includes @response.body, values
Testing Actions in Different Files
Request tests can be complicated, and in many cases, a single action can take hundreds of lines to test for its complicated scenerios, including edge cases. It's better to separate complicated test cases into different files.
It is easy to separate model tests because we can name the classes as we want. However, in controller tests, Minitest checks the class name and find the correct controllers for us, which make us a little hard to separate them. Let's suppose we want to separate posts#index
and posts#show
like this:
- test
- controllers
- posts_controller
- index_test.rb
- show_test.rb
And the files look like this:
# index_test.rb
require 'test_helper'
class PostsController::IndexTest < ActionController::TestCase
def test_html
# Some tests ...
end
def test_json
# Some tests ...
end
end
and another file:
# show_test.rb
require 'test_helper'
class PostsController::ShowTest < ActionController::TestCase
def test_respond_200
# Some tests ...
end
def test_without_id
# Some tests ...
end
end
In this case, Minitest can identify that these test classes are for specific controllers, so we name the methods with all different cases we want to test.
Testing Routes in Controllers
Testing routes in Minitest is integrated into controller tests under ActionController::TestCase
. In this case, we should create another file under the same file structure to test those routes under a controller. It will look something like this:
# test/controllers/posts_controller/test_routes.rb
require 'test_helper'
class PostsController::RoutesTest < ActionController::TestCase
def test_routes
assert_routing '/posts', controller: "posts", action: "index"
assert_routing '/posts/1', controller: "posts", action: "show", id: "1"
end
end