The directory structure

Specs are usually placed in a canonical directory structure that describes their purpose:

Application developers are free to use a different directory structure. In order to include the correct rspec-rails support functions, the specs need to have the appropriate corresponding metadata :type value:

For example, say the spec for the ThingsController is located in spec/legacy/things_controller_spec.rb. Simply tag the spec’s RSpec.describe block with the type: :controller metadata:

  # spec/legacy/things_controller_spec.rb
  RSpec.describe ThingsController, type: :controller do
    describe "GET index" do
      # Examples
    end
  end

Note: Standard RSpec specs do not require any additional metadata by default.

Check out the rspec-core documentation on using metadata for more details.

Automatically Adding Metadata

RSpec versions before 3.0.0 automatically added metadata to specs based on their location on the filesystem. This was both confusing to new users and not desirable for some veteran users.

This behaviour must be explicitly enabled:

  # spec/rails_helper.rb
  RSpec.configure do |config|
    config.infer_spec_type_from_file_location!
  end

Since this assumed behavior is so prevalent in tutorials, the default configuration generated by rails generate rspec:install enables this.

If you follow the above listed canonical directory structure and have configured infer_spec_type_from_file_location!, RSpec will automatically include the correct support functions for each type.

If you want to set metadata for a custom directory that doesn’t follow fit the canonical structure above, you can do the following:

  # set `:type` for serializers directory
  RSpec.configure do |config|
    config.define_derived_metadata(:file_path => Regexp.new('/spec/serializers/')) do |metadata|
      metadata[:type] = :serializer
    end
  end

Tips on Spec Location

It is suggested that the spec/ directory structure generally mirror both app/ and lib/. This makes it easy to locate corresponding code and spec files.

Example:

  app
  ├── controllers
  │   ├── application_controller.rb
  │   └── books_controller.rb
  ├── helpers
  │   ├── application_helper.rb
  │   └── books_helper.rb
  ├── models
  │   ├── author.rb
  │   └── book.rb
  └── views
      ├── books
      └── layouts
  lib
  ├── country_map.rb
  ├── development_mail_interceptor.rb
  ├── environment_mail_interceptor.rb
  └── tasks
      └── irc.rake
  spec
  ├── controllers
  │   └── books_controller_spec.rb
  ├── country_map_spec.rb
  ├── features
  │   └── tracking_book_delivery_spec.rb
  ├── helpers
  │   └── books_helper_spec.rb
  ├── models
  │   ├── author_spec.rb
  │   └── book_spec.rb
  ├── rails_helper.rb
  ├── requests
  │   └── books_spec.rb
  ├── routing
  │   └── books_routing_spec.rb
  ├── spec_helper.rb
  ├── tasks
  │   └── irc_spec.rb
  └── views
      └── books

Standard Rails specs must specify the :type metadata

Given a file named “spec/functional/widgetscontrollerspec.rb” with:

require "rails_helper"

RSpec.describe WidgetsController, type: :controller do
  it "responds successfully" do
    get :index
    expect(response.status).to eq(200)
  end
end

When I run rspec spec

Then the example should pass.

Non-rails related specs do not require :type metadata by default

Given a file named “spec/ledger/entry_spec.rb” with:

require "spec_helper"

Entry = Struct.new(:description, :us_cents)

RSpec.describe Entry do
  it "has a description" do
    is_expected.to respond_to(:description)
  end
end

When I run rspec spec

Then the example should pass.

Inferring spec type from the file location adds the appropriate metadata

Given a file named “spec/controllers/widgetscontrollerspec.rb” with:

require "rails_helper"

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

RSpec.describe WidgetsController do
  it "responds successfully" do
    get :index
    expect(response.status).to eq(200)
  end
end

When I run rspec spec

Then the example should pass.

Specs in canonical directories can override their inferred types

Given a file named “spec/routing/duckduckroutingspec.rb” with:

require "rails_helper"

Rails.application.routes.draw do
  get "/example" => redirect("http://example.com")
end

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

# Due to limitations in the Rails routing test framework, routes that
# perform redirects must actually be tested via request specs
RSpec.describe "/example", type: :request do
  it "redirects to example.com" do
    get "/example"
    expect(response).to redirect_to("http://example.com")
  end
end

When I run rspec spec

Then the example should pass.