Skip to main content

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:

  • Model specs: type: :model
  • Controller specs: type: :controller
  • Request specs: type: :request
  • Feature specs: type: :feature
  • View specs: type: :view
  • Helper specs: type: :helper
  • Mailer specs: type: :mailer
  • Routing specs: type: :routing
  • Job specs: type: :job
  • System specs: type: :system

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 ├── enviroment_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/widgets_controller_spec.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.

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/widgets_controller_spec.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/duckduck_routing_spec.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.