The directory structure
Specs are usually placed in a canonical directory structure that describes their purpose:
Model specs reside in the
spec/modelsdirectoryController specs reside in the
spec/controllersdirectoryRequest specs reside in the
spec/requestsdirectory. The directory can also be namedintegrationorapi.Feature specs reside in the
spec/featuresdirectoryView specs reside in the
spec/viewsdirectoryHelper specs reside in the
spec/helpersdirectoryMailer specs reside in the
spec/mailersdirectoryRouting specs reside in the
spec/routingdirectoryJob specs reside in the
spec/jobsdirectorySystem specs reside in the
spec/systemdirectory
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
├── 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.