LogoDTreeLabs

Rspec - access instance variable of controller action in tests

Deepak JadhavBy Deepak Jadhav in RailsRspec on March 14, 2022

We can test the controller actions, based on the response returned. If the controller actions renders JSON / XML response, it can be easily tested in controller action rspec tests. But, if a controller action renders Rails Views, testing the response can be tedious.

In such cases, the controller action assigns values to instance variables. We can test values of such instance variables using assigns method. This post will demonstrate how can we access instance variables of controller action in rspec.

Background

There are different view points on what should be tested when writing tests for controller actions. Summarising a few popular opinions below:

View point 1: Verify the response code, rendered views, any database changes

Attaching a comment from DHH below.

Testing what instance variables are set by your controller is a bad idea. That's grossly overstepping the boundaries of what the test should know about. You can test what cookies are set, what HTTP code is returned, how the view looks, or what mutations happened to the DB, but testing the innards of the controller is just not a good idea.

View point 2: Verify values set in the controller action, as views depend on the instance variables defined by the controller action

Attaching a comment from listrophy below.

When testing a unit, you want to see the result of calling a function. That result comprises a) return values and b) external state modifications. In nearly any other case, internal (ivar) modification should not be tested, but this is not true with controllers. Why? Because the next high-level step of the render chain is to copy the controller's ivars to the view. That's an external—albeit delayed—state change, and the fact that it occurs with ivars is merely an implementation detail/convenience.

Upon some discussion on the pull request assigns was deprecated from Rails core, it has been extracted to rails-controller-testing gem. It has assigns and assert_template methods.

Using assigns to verify instance variables in rspec

Suppose, we have following controller action renders the HTML view and it has only @student instance variable to be used by its view file show.html.erb.

class StudentsController < ApplicationController
  def show
    @student = Student.find_by(id: params[:id])
  end
end

and the Rspec file for above controller action is

describe StudentsController, type: :controller do
  context "students#show"
    it "should fetch student details" do
      get :show, params: { id: 1 }
      # response won't contain the @student then how can we test it?
    end
  end
end

Though, the controller action is executed successfully the response won't contain the value of @student instance variable and @student instance variable can't be used directly in rspec as it is not declared in the scope of specs. So how can we access it?

Rails action controller testing gem provides as access to a hash of all the instance variables present in the controller with instance variable name as key. assigns(key) returns value of instance variable with name key

Now, a test for above controller action can be written as given below.

describe StudentsController, type: :controller do
  context "students#show"
    it "should fetch student details" do
      get :show, params: { id: 1 }
      student = assigns(:student)
      expect(student).not_to be_nil
      expect(student[:name]).to eq("Bob")
    end
  end
end