Skip to main content
Skip table of contents

Request Specs

Last Updated:

Request specs are integration tests for a Rails API. They act as a mini “feature” test for an endpoint or group of endpoints. Request specs test the whole stack including routing, middleware, and rack request and responses. Whereas a controller spec is a unit test for a controller action(s). RSpec recommends request specs over controller specs because they are faster and are more realistic. APIs usually only have request specs.

Unconventional example

Request specs aren’t a better unit test for controllers. In practice request specs typically align with a controller because they both handle a group of related endpoints. It’s best that this correlation is implied rather than explicit. Controller specs test actions and request specs test endpoints. As controllers and endpoints drifts from the conventions, the difference between what request specs and controllers test becomes more apparent.

Here are an example to demonstrate the difference in what’s being tested

RUBY
# app/controllers/v1/cars_controller.rb

class V1::CarsController < ApplicationController
  # GET v1/automobiles
  def index
    render json: Vehicle.all.to_json
  end

  # POST v1/automobiles
  def create
    vehicle = Vehicle.create(car_params)
    render json: vehicle.to_json
  end
end

In the API controller you can see three potential options for the request spec naming. First the controller name: V1::CarsController. Second the object (or concept) involved: Vehicle. Third is the endpoint: v1/automobiles.

Ideally each component of the stack would be named using “vehicle”, so naming the request spec is obvious. In practice that doesn’t always happen, so it’s better to use the name from what’s being tested to avoid confusion and prevent guessing. In the above case we are testing the automobiles endpoints.

The corresponding request spec should look like this

RUBY
# spec/requests/v1/autombiles

Rspec.describe 'V1::Automobiles', type: :request do 
  describe 'GET v1/automobiles' do
  end
  
  describe 'POST v1/automobiles' do 
  end
end

Best practices

Naming

  1. Request spec's path should be based on route namespaces/scopes/nested resources

  2. Request spec's filename should be based on the object, conceptual idea, or last path in route

  3. Request spec's top-level example group name should be based on file path & name

Route

/v0/messaging/health/messages/:id/attachments/:id

Namespaces

/v0/messaging/health/messages

Objects/last path

attachments

translates to:

File path

spec/requests/v0/messaging/health/messages

File name

attachments_spec.rb

Example group name

'V0::Messaging::Health::Messages::Attachments'

Exceptions

If the entire stack uses the same root or base name (e.g., Vehicle) except for the routes (e.g., v1/automobiles) it’s acceptable to use Vehicle instead of Automobile for naming the request spec. In this case, it’s better to have one exception instead of two.

Nested example group names

Nested example groups should test one specific endpoint. The name should include only the HTTP verb and the path. Context groups can be used if further nesting is needed to test different states or situations within the nested describe group.

RUBY
# bad

describe '#index' do
describe 'get all users'
describe 'when params are valid return all users'

# good

describe 'POST api/v1/users' do 
  context 'when params are valid' do 
  end
  
  context 'when params are invalid' do 
  end
end

Multi-controller request specs

Although request specs typically only involve one controller, it can test multiple controllers that have related endpoints. In this case, naming can be more generic or focus on the common namespace. Here’s an example of how this would be done.

You have a chat app with messages. You may have the following controllers:

  • MessagesController

  • Messages::DraftsControllers

  • Messages::TrashesController

  • Messages::UnreadsController

Draft, trash, unread are states or scopes of a message, not other other models. So, it would be reasonable to create a single request spec that tests these related endpoints:

RUBY
# spec/requests/messages_spec.rb

Rspec.describe 'Messages API', type: :request do 
  describe 'GET messages' do
  end
  
  describe 'POST messages' do
  end
  
  describe 'PUT messages' do
  end
  
  describe 'DELETE messages' do
  end
  
  describe 'GET messages/drafts' do 
  end
  
  describe 'GET messages/trashes' do 
  end
  
  describe 'GET messages/unreads' do 
  end
end

Although they don’t need to be under the same namespace, the common namespace is an indicator that the endpoints could be tested in a single spec. Related endpoints can use the same spec file as long as they are conceptually closely related. When in doubt use separate files.

Defining related endpoints

This means the endpoints have the same or nearly the same conceptual object.

Let’s consider a couple examples.

  1. Associations

  • v1/engines

  • v1/engines/:id/parts

In this example an engine has many parts and the parts are nested resources in an engine. They might seem closes related because of the nesting, but are different conceptual objects. A fuel injector isn’t an engine and an engine isn’t just a fuel injector. They should have separate files.

  1. Beyond CRUD

  • v1/users

  • v1/users/search

  • v1/users/filter

In this example you could have three controllers for users, searching, and filtering. Conceptually, they involve the same conceptual object, User. These three sets of endpoints could use the same spec file. Another indicator for a single file is a lack of associations. If has_many or belongs_to are involved in the conceptual object, then use different files. Note: just because they could use the same spec files, doesn’t mean they need to use the same file.

  1. Single Controller and Multiple Objects

Sometimes a single controller returns multiple objects or models. At first glance, these endpoints seems closely related because they are in the same controller. However, FormA and Attachment are separate conceptual ideas. They should have separate request specs (and should be in different controllers.)

RUBY
class V1::FormA < ApplicationController

  # GET v1/form_a/:id
  def show; end
  
  # POST v1/form_a
  def create; end
  
  # GET v1/form_a/:id/attachment
  def attachments; end
  
end

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.