Older Rails Controller Tests

In Rails 5, two features of controller testing, one commonly used, one less so, were deprecated. This deprecation has the effect of limiting the scope of controller testing. In the eyes of the Rails core, this scope should be picked up by integration testing, though we’d suggest that some of it should be picked up by moving code out of the controller and unit-testing it. The following is a quick guide to what Rails 4 controller tests looked like, as we’ll probably see a bunch of them.

Controller test requests

Rails 4 provided the same set of methods we’ve covered for creating a request: get, post and their friends. However, the method parameters were different:

get :show, {id: @task.id}, {user_id: "3",
    current_project: @project.id.to_s}, {notice: "flash test"}

The get method

Here, the method name, get, is the HTTP verb being simulated, sort of. While the controller test will set the HTTP verb if for some reason we query the Rails request object, it does not check the Rails routing table to see if that action is reachable using that HTTP verb. As a result, we can’t test routing via a controller test. Refer back to the lesson “Testing Routes” to see how Rails provides a mechanism for testing routes.

First and second arguments

The first argument, in this case :show, is the controller action being called. The second argument, {id: @task.id}, is a hash that becomes the params hash in the controller action. In the controller action called from this test, we would expect params[:id] to equal @task.id. The Rails form name-parsing trick is not used here. If we want to simulate a form upload, we use a multilevel hash directly, as in user: {name: "Noel", email: "noel@noelrappin.com"}, which implies params[:user][:name] == "Noel" in the controller.

Hash argument

Any value passed in this hash argument is converted to a string—specifically, to_param is called on it. So we can do something like id: 3, confident that it will be "3" in the controller. This, by the way, is a welcome change in recent versions of Rails. Older versions did not do this conversion, which led to heads occasionally pounding against walls.

Third and fourth arguments

The third and fourth arguments to these controller methods are optional and rarely used. The third argument sets key-value pairs for the session object, which is useful in testing multistep processes that use the session for continuity. The fourth argument represents the Rails flash-object, which is rarely useful but if for some reason the incoming flash is important for our logic, there it is.

Access request object

We may occasionally want to do something fancier to the simulated request. In a controller test we have access to the request object as @request, and access to the controller object as @controller. (As we’ll see in the next topic, we also have the @response object). We can get at the HTTP headers using the hash @request.headers.

The xml_http_request controller action method

There is one more controller action method: xml_http_request (also aliased to xhr). This simulates a classic Ajax call to the controller and has a slightly different signature:

it "makes an ajax call" do
  xhr :post, :create, :task => {:id => "3"}
end

The method name is xhr. The first argument is the HTTP verb associated with the xhr call, and the remaining arguments are the arguments to all the other controller-calling methods in the same order:

  • The action argument
  • The params argument
  • The session argument
  • The flash argument

The xhr call sets the appropriate headers such that the Rails controller will appropriately be able to consider the request an Ajax request (meaning .js format blocks will be triggered), then simulates the call based on its arguments.

Evaluating Controller Results

Rails 4 controller tests have a few other features that were deprecated in Rails 5. In Rails 4 we could use the render_template matcher to assert which template rendered a method.

Instance values

Rails 4 also exposed an assigns hash that had all the instance values that the controller passes to the view. This is deprecated now, but it was at one time a very common way to test Rails controllers. RSpec controller tests do not render the view by default, and we should use request or system specs if we would like that behavior.

Get hands-on with 1200+ tech skills courses.