The directory structure
Specs are usually placed in a canonical directory structure that describes their purpose:
-
Model specs reside in the
spec/models
directory -
Controller specs reside in the
spec/controllers
directory -
Request specs reside in the
spec/requests
directory. The directory can also be namedintegration
orapi
. -
Feature specs reside in the
spec/features
directory -
View specs reside in the
spec/views
directory -
Helper specs reside in the
spec/helpers
directory -
Mailer specs reside in the
spec/mailers
directory -
Routing specs reside in the
spec/routing
directory -
Job specs reside in the
spec/jobs
directory -
System specs reside in the
spec/system
directory
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.
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/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.