Loading...
New webinar: "The Remote Job Search: My Microverse Journey" with graduate Paul Rail
Watch Now

In this article, you will gain an in-depth understanding of controller specs, request specs and feature specs. You’ll learn how to use Capybara to describe a typical user story where all interactions are driven via the user interface, i.e. User visits a home page, gets redirected to sign in, fills the login form and clicks the login button. 

I will often refer to Request Specs as a Controller Spec since they both test the actions in the controller.

Have you noticed that if you try to generate a controller spec in your application, it creates a request spec instead? It can only be created manually, as controller specs are discouraged, but not deprecated.

The RSpec team actually officially discourages the use of controller specs, as well as adding the rails-controller-testing gem to your application; 

“The official recommendation of the Rails team and the RSpec core team is to write request specs instead. Request specs allow you to focus on a single controller action, but unlike controller tests involve the router, the middleware stack, and both rack requests and responses. This adds realism to the test that you are writing, and helps avoid many of the issues that are common in controller specs.” See the full article for more details.

Working With Rails Controller Specs: How Are Requests Made?

Controllers are classes, like models. You may not have thought much about it because Rails creates a new instance of controller classes for us, and we tend to think of them as actions.

Controllers are unique types though, because they function inside of a request/response cycle. They expect the browser to send them a request then they put together a response to send back. Controllers are also in charge of: 

  • rendering templates 
  • redirecting actions
  • setting values for cookies & sessions

To test the controller, we will need some extra tools to work comfortably with controller features. 

RSpec gives us helpers for simulating requests, attributes for accessing values that are being assembled by the controller, and additional matchers for setting expectations on responses.

Controller Spec HTTP Request

  • get(action, options)
  • post(action, options)
  • patch(action, options)
  • put(action, options)
  • delete(action, options)

The HTTP Requests correspond to having a RESTful application, and you likely have been working with that in Rails, as Rails prefers you work with it in a RESTful way. A GET request is the normal type of request you get when requesting a URL, and a POST request is the normal request you get when submitting a form.

The methods’ first argument in each case is followed by the action e.g. index, new, edit, add_to_cart, etc.. The option is the hash of any other value you want to pass in for a GET request, which means any URL parameter. For a POST request, that means any form data.


get(:index) / get("/users/index") / get users_path 

You don’t need to call the controller — call get(:index) will know that it refers to the #index the action of the current controller its being spec.

With params : get(:index, page: 2, search: "essien") 

For post : post(:create, users: {first_name: 'Uduak', last_name: 'Essien'})

While I enjoyed the straightforward way to test the controller actions directly (get(:index)), you should note things work a bit differently in the request specs. It uses Rack::Test’s simple methods for passing HTTP requests, along with parameters, to your app.

Using `get(:index)` inside request specs will throw an error.


Users gets #index
     Failure/Error: get(:index)
     URI::InvalidURIError:
       bad URI(is not URI?): "http://www.example.com:80index"

You should get the actual path using `rake routes` and use something like `get users_path`

Controller Spec Attributes

Once we can simulate a request, we need a way to look at the things the controller is doing. What changes is it making? 

This can be achieved by using a few special objects that RSpec-Rails makes available to us:

  • Controller
  • Request
  • Response

There are also some other useful attributes when a controller receives a request. A lot of what these attributes do is assign objects; to an instance variable, to cookies, and to session files. 

RSpec-Rails gives us four attributes that grant us access to hashes containing these values. This allows us to inspect them and to write expectations about what values they should contain. Below are the four attributes:

  • assigns: a hash of instance variables, assigned in the action that is available to the view. You will notice it has ‘s’ at the end, which is different from what we discussed earlier in the second part of this article: Understanding model and helper specs, under the ‘working with rails helper specs’ section; to set an instance variable in the helper method.
  • sessions: the object being saved in the session file.
  • flash: objects are flashed messages.
  • cookies: cookies have been sent back to users on the request.

class UsersController < ApplicationController

 def index
   @users = User.all
 end
...
----------------------------------------------------
RSpec.describe 'Users', type: :request do

 it "assigns all users to @users" do
   get users_path
   expect(assigns(:users)).to eq(User.all)
 end
....

If you take a look at the code snippet above, our index action @users will be used by the view to render the users. Our expectation is that we are assigning the object @users, and equating it to User.all. 

Cookies have one other thing you need to watch out for; when a browser sends in a request, it sends the set cookies with the request. Likewise, when we send back the response, it sends the set cookies with the response.

If I say cookies['logged_in'], then which cookies am I asking for? The ones coming in or going out? The answer is; it combines both. 

So, it’s often better to use:

  • request.cookies['logged_in']
  • response.cookies['logged_in'] 

That way, it becomes absolutely clear which we are targeting.

Request Specs: Responses/Matchers

A number of built-in matchers are shipped in with rspec-expectations and each matcher is defined by both positive and negative expectations using expect(..).to or expect(..).not_to respectively on an object. Now let's take a look at a few examples:

  • expect(response).to render_template(template) 

Above, we have the render_template matcher. When we expect the response to render a certain template, then we can pass in the template as an argument.

  • expect(response).to redirect_to(path) 

The redirect_to matcher expects a response to redirect to a certain path.

  • expect(response).to have_http_status(status) 

This is another matcher for checking http_status when a certain response is received. The common status code you will encounter includes:

  • 200,  :ok 
  • 403, :forbidden 
  • 404, :not_found 
  • 301, :moved_permanently 
  • 302, :found 
  • 500, :internal_server_error 
  • 502, :bad_gateway

You can always review Rails status code if you want to find more.


RSpec.describe 'Users', type: :request do

 describe 'GET #index' do
   before(:example) { get users_path }  # get(:index)

   it "is a success" do
     expect(response).to have_http_status(:ok)
   end
   it "renders 'index' template" do
     expect(response).to render_template('index')
   end
 end
....

In the first example, after we have called the get(:index) or get users_path, we expect a response to have an HTTP status code of ‘ok’.

The second test, is to test whether it rendered the template index under the views/users directory.

Integration Test Using Capybara

Capybara is an exceptionally great tool for performing an integration test with RSpec because it helps you perform end-to-end tests on your applications. It does this by simulating how a real user would interact with your application.

You can use it to work through your application routes as if you are actually visiting the pages and performing actions on each page.

Setting up Capybara is easy, just add the gem file and you are good to go.


group :development, :test do
 gem 'capybara'
 gem 'rspec-rails'
end

When we are using Capybara, we are testing for features, so we should create a folder under our spec folder called features. specs/features. Capybara documentation does a great job of demonstrating some test cases as well.

Let’s look at a few examples to get ourselves familiar with the concepts.

Under my specs/feature the directory I will create login_spec.rb 


require 'rails_helper'
describe 'the signin process', type: :feature do
 before :each do
   User.create(email: 'user1@gmail.com', password: 'password', name: 'User1')
 end
 it 'signs @user in' do
   visit '/users/sign_in'
   fill_in 'Email', with: 'user1@gmail.com'
   fill_in 'Password', with: 'password'
   click_button 'Log in'
  expect(current_path).to eq(root_path)
  expect(page).to have_text('Signed in successfully.')
 end
end

We first create a user inside the before action, then, in our example we visit the login page, enter the email and password, and click on the login button. Our expectations will now be set to meet each response/request.

You can take a look at the Capybara documentation for more examples and test cases.

Conclusion

In the first article of this series, Understanding Test-Driven Development with RSpec in Ruby on Rails, we looked into the importance of understanding why, and when, to test our code, as well as testing best practices. We also explained test-driven development with Rspec in Ruby on Rails.

In the second article, Understanding RSpec Model and Helper Specs, we went over getting started with running tests on your model and helper specs, with examples to get you up to speed. We also looked at the most common roadblocks when working with them, and shared tips and advice for using them. In our model specs section, we talked about shoulda-matchers, an excellent tool for writing tests and validations. It will make life more comfortable so you should check it out if you haven’t done so already.

By now, you should be well equipped to write request specs with feature specs using Capybara.

Having gone through all articles in this series, writing Rspec testing for your applications should not be as challenging anymore. So, let validating behaviour and alerting developers of any mishaps behind potential code changes be the overall goal behind testing.

If you have any comments or thoughts about these articles, feel free to reach out. Happy bug crushing!

Photo by Linus Rogge on Unsplash

Subscribe to our Newsletter

Get Our Insights in Your Inbox

Career advice, the latest coding trends and languages, and insights on how to land a remote job in tech, straight to your inbox.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.