In the final article of this two part series (see the other article here) you'll gain an in-depth understanding of controller specs, request specs and feature specs. You'll also learn how to use Capybara to describe a typical user story where all interactions are driven via the user interface. For example, when a user visits a home page, gets redirected to sign in, fills the login form and clicks the login button.
I'll 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.
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:
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.
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.
{% code-block language="js" %}
get(:index) / get("/users/index") / get users_path
{% code-block-end %}
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.
{% code-block language="js" %}
Users gets #index
Failure/Error: get(:index)
URI::InvalidURIError:
bad URI(is not URI?): "http://www.example.com:80index"
{% code-block-end %}
You should get the actual path using `rake routes` and use something like `get users_path`
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:
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:
{% code-block language="js" %}
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
{% code-block-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:
That way, it becomes absolutely clear which we are targeting.
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:
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.
The redirect_to matcher expects a response to redirect to a certain path.
This is another matcher for checking http_status when a certain response is received. The common status code you will encounter includes:
You can always review Rails status code if you want to find more.
{% code-block language="js" %}
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
{% code-block-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.
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.
{% code-block language="js" %}
group :development, :test do
gem 'capybara'
gem 'rspec-rails'
end
{% code-block-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
{% code-block language="js" %}
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
{% code-block-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.
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.
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.
Happy bug crushing!
Career advice, the latest coding trends and languages, and insights on how to land a remote job in tech, straight to your inbox.