Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rubocop/rspec-style-guide

Best practices for writing your specs!
https://github.com/rubocop/rspec-style-guide

best-practices rspec ruby style style-guide

Last synced: about 1 month ago
JSON representation

Best practices for writing your specs!

Awesome Lists containing this project

README

        

= RSpec Style Guide
:idprefix:
:idseparator: -
:sectanchors:
:sectlinks:
:toc: preamble
:toclevels: 1
ifndef::backend-pdf[]
:toc-title: pass:[

Table of Contents

]
endif::[]
:source-highlighter: rouge

== Introduction

[quote, Officer Alex J. Murphy / RoboCop]
____
Role models are important.
____

ifdef::env-github[]
TIP: You can find a beautiful version of this guide with much improved navigation at https://rspec.rubystyle.guide.
endif::[]

This RSpec style guide outlines the recommended best practices for real-world programmers to write code that can be maintained by other real-world programmers.

https://github.com/rubocop/rubocop[RuboCop], a static code analyzer (linter) and formatter, has a https://github.com/rubocop/rubocop-rspec[`rubocop-rspec`] extension, provides a way to enforce the rules outlined in this guide.

NOTE: This guide assumes you are using RSpec 3 or later.

You can generate a PDF copy of this guide using https://asciidoctor.org/docs/asciidoctor-pdf/[AsciiDoctor PDF], and an HTML copy https://asciidoctor.org/docs/convert-documents/#converting-a-document-to-html[with] https://asciidoctor.org/#installation[AsciiDoctor] using the following commands:

[source,shell]
----
# Generates README.pdf
asciidoctor-pdf -a allow-uri-read README.adoc

# Generates README.html
asciidoctor README.adoc
----

[TIP]
====
Install the `rouge` gem to get nice syntax highlighting in the generated document.

[source,shell]
----
gem install rouge
----
====

== How to Read This Guide

The guide is separated into sections based on the different pieces of an entire spec file. There was an attempt to omit all obvious information, if anything is unclear, feel free to open an issue asking for further clarity.

== A Living Document

Per the comment above, this guide is a work in progress - some rules are simply lacking thorough examples, but some things in the RSpec world change week by week or month by month.
With that said, as the standard changes this guide is meant to be able to change with it.

== Layout

=== Empty Lines inside Example Group[[empty-lines-after-describe]]

Do not leave empty lines after `feature`, `context` or `describe` descriptions.
It doesn't make the code more readable and lowers the value of logical chunks.

[source,ruby]
----
# bad
describe Article do

describe '#summary' do

context 'when there is a summary' do

it 'returns the summary' do
# ...
end
end
end
end

# good
describe Article do
describe '#summary' do
context 'when there is a summary' do
it 'returns the summary' do
# ...
end
end
end
end
----

=== Empty Line between Example Groups [[empty-lines-between-describes]]

Leave one empty line between `feature`, `context` or `describe` blocks.
Do not leave empty line after the last such block in a group.

[source,ruby]
----
# bad
describe Article do
describe '#summary' do
context 'when there is a summary' do
# ...
end
context 'when there is no summary' do
# ...
end

end
describe '#comments' do
# ...
end
end

# good
describe Article do
describe '#summary' do
context 'when there is a summary' do
# ...
end

context 'when there is no summary' do
# ...
end
end

describe '#comments' do
# ...
end
end
----

=== Empty Line After `let`[[empty-lines-after-let]]

Leave one empty line after `let`, `subject`, and `before`/`after` blocks.

[source,ruby]
----
# bad
describe Article do
subject { FactoryBot.create(:some_article) }
describe '#summary' do
# ...
end
end

# good
describe Article do
subject { FactoryBot.create(:some_article) }

describe '#summary' do
# ...
end
end
----

=== Let Grouping

Only group `let`, `subject` blocks and separate them from `before`/`after` blocks.
It makes the code much more readable.

[source,ruby]
----
# bad
describe Article do
subject { FactoryBot.create(:some_article) }
let(:user) { FactoryBot.create(:user) }
before do
# ...
end
after do
# ...
end
describe '#summary' do
# ...
end
end

# good
describe Article do
subject { FactoryBot.create(:some_article) }
let(:user) { FactoryBot.create(:user) }

before do
# ...
end

after do
# ...
end

describe '#summary' do
# ...
end
end
----

=== Empty Lines around Examples[[empty-lines-around-it]]

Leave one empty line around `it`/`specify` blocks. This helps to separate the expectations from their conditional logic (contexts for instance).

[source,ruby]
----
# bad
describe '#summary' do
let(:item) { double('something') }

it 'returns the summary' do
# ...
end
it 'does something else' do
# ...
end
it 'does another thing' do
# ...
end
end

# good
describe '#summary' do
let(:item) { double('something') }

it 'returns the summary' do
# ...
end

it 'does something else' do
# ...
end

it 'does another thing' do
# ...
end
end
----

== Example Group Structure

=== Leading `subject`

When `subject` is used, it should be the first declaration in the example group.

[source,ruby]
----
# bad
describe Article do
before do
# ...
end

let(:user) { FactoryBot.create(:user) }
subject { FactoryBot.create(:some_article) }

describe '#summary' do
# ...
end
end

# good
describe Article do
subject { FactoryBot.create(:some_article) }
let(:user) { FactoryBot.create(:user) }

before do
# ...
end

describe '#summary' do
# ...
end
end
----

=== Declaring `subject`, `let!`/`let` and `before`/`after` hooks

When declaring `subject`, `let!`/`let` and `before`/`after` hooks they should be in the following order:

* `subject`
* `let!`/`let`
* `before`/`after`

[source,ruby]
----
# bad
describe Article do
before do
# ...
end

after do
# ...
end

let(:user) { FactoryBot.create(:user) }
subject { FactoryBot.create(:some_article) }

describe '#summary' do
# ...
end
end

# good
describe Article do
subject { FactoryBot.create(:some_article) }
let(:user) { FactoryBot.create(:user) }

before do
# ...
end

after do
# ...
end

describe '#summary' do
# ...
end
end
----

=== Use Contexts

Use contexts to make the tests clear, well organized, and easy to read.

[source,ruby]
----
# bad
it 'has 200 status code if logged in' do
expect(response).to respond_with 200
end

it 'has 401 status code if not logged in' do
expect(response).to respond_with 401
end

# good
context 'when logged in' do
it { is_expected.to respond_with 200 }
end

context 'when logged out' do
it { is_expected.to respond_with 401 }
end
----

=== Context Cases

`context` blocks should pretty much always have an opposite negative case.
It is a code smell if there is a single context (without a matching negative case), and this code needs refactoring, or may have no purpose.

[source,ruby]
----
# bad - needs refactoring
describe '#attributes' do
context 'the returned hash' do
it 'includes the display name' do
# ...
end

it 'includes the creation time' do
# ...
end
end
end

# bad - the negative case needs to be tested, but isn't
describe '#attributes' do
context 'when display name is present' do
before do
article.display_name = 'something'
end

it 'includes the display name' do
# ...
end
end
end

# good
describe '#attributes' do
subject(:attributes) { article.attributes }
let(:article) { FactoryBot.create(:article) }

context 'when display name is present' do
before do
article.display_name = 'something'
end

it { is_expected.to include(display_name: article.display_name) }
end

context 'when display name is not present' do
before do
article.display_name = nil
end

it { is_expected.not_to include(:display_name) }
end
end
----

=== `let` Blocks

Use `let` and `let!` for data that is used across several examples in an example group.
Use `let!` to define variables even if they are not referenced in some of the examples, e.g. when testing balancing negative cases.
Do not overuse ``let``s for primitive data, find the balance between frequency of use and complexity of the definition.

[source,ruby]
----
# bad
it 'finds shortest path' do
tree = Tree.new(1 => 2, 2 => 3, 2 => 6, 3 => 4, 4 => 5, 5 => 6)
expect(dijkstra.shortest_path(tree, from: 1, to: 6)).to eq([1, 2, 6])
end

it 'finds longest path' do
tree = Tree.new(1 => 2, 2 => 3, 2 => 6, 3 => 4, 4 => 5, 5 => 6)
expect(dijkstra.longest_path(tree, from: 1, to: 6)).to eq([1, 2, 3, 4, 5, 6])
end

# good
let(:tree) { Tree.new(1 => 2, 2 => 3, 2 => 6, 3 => 4, 4 => 5, 5 => 6) }

it 'finds shortest path' do
expect(dijkstra.shortest_path(tree, from: 1, to: 6)).to eq([1, 2, 6])
end

it 'finds longest path' do
expect(dijkstra.longest_path(tree, from: 1, to: 6)).to eq([1, 2, 3, 4, 5, 6])
end
----

=== Instance Variables

Use `let` definitions instead of instance variables.

[source,ruby]
----
# bad
before { @name = 'John Wayne' }

it 'reverses a name' do
expect(reverser.reverse(@name)).to eq('enyaW nhoJ')
end

# good
let(:name) { 'John Wayne' }

it 'reverses a name' do
expect(reverser.reverse(name)).to eq('enyaW nhoJ')
end
----

=== Shared Examples

Use shared examples to reduce code duplication.

[source,ruby]
----
# bad
describe 'GET /articles' do
let(:article) { FactoryBot.create(:article, owner: owner) }

before { page.driver.get '/articles' }

context 'when user is the owner' do
let(:user) { owner }

it 'shows all owned articles' do
expect(page.status_code).to be(200)
contains_resource resource
end
end

context 'when user is an admin' do
let(:user) { FactoryBot.create(:user, :admin) }

it 'shows all resources' do
expect(page.status_code).to be(200)
contains_resource resource
end
end
end

# good
describe 'GET /articles' do
let(:article) { FactoryBot.create(:article, owner: owner) }

before { page.driver.get '/articles' }

shared_examples 'shows articles' do
it 'shows all related articles' do
expect(page.status_code).to be(200)
contains_resource resource
end
end

context 'when user is the owner' do
let(:user) { owner }

include_examples 'shows articles'
end

context 'when user is an admin' do
let(:user) { FactoryBot.create(:user, :admin) }

include_examples 'shows articles'
end
end

# good
describe 'GET /devices' do
let(:resource) { FactoryBot.create(:device, created_from: user) }

it_behaves_like 'a listable resource'
it_behaves_like 'a paginable resource'
it_behaves_like 'a searchable resource'
it_behaves_like 'a filterable list'
end
----

=== Redundant `before(:each)`

Don't specify `:each`/`:example` scope for `before`/`after`/`around` blocks, as it is the default.
Prefer `:example` when explicitly indicating the scope.

[source,ruby]
----
# bad
describe '#summary' do
before(:example) do
# ...
end

# ...
end

# good
describe '#summary' do
before do
# ...
end

# ...
end
----

=== Ambiguous Hook Scope

Use `:context` instead of the ambiguous `:all` scope in `before`/`after` hooks.

[source,ruby]
----
# bad
describe '#summary' do
before(:all) do
# ...
end

# ...
end

# good
describe '#summary' do
before(:context) do
# ...
end

# ...
end
----

=== Avoid Hooks with `:context` Scope

Avoid using `before`/`after` with `:context` scope.
Beware of the state leakage between the examples.

== Example Structure

=== Expectation per Example[[one-expectation]]

For examples two styles are considered acceptable.
The first variant is separate example for each expectation, which comes with a cost of repeated context initialization.
The second variant is multiple expectations per example with `aggregate_failures` tag set for a group or example.
Use your best judgement in each case, and apply your strategy consistently.

[source,ruby]
----
# good - one expectation per example
describe ArticlesController do
#...

describe 'GET new' do
it 'assigns a new article' do
get :new
expect(assigns[:article]).to be_a(Article)
end

it 'renders the new article template' do
get :new
expect(response).to render_template :new
end
end
end

# good - multiple expectations with aggregated failures
describe ArticlesController do
#...

describe 'GET new', :aggregate_failures do
it 'assigns new article and renders the new article template' do
get :new
expect(assigns[:article]).to be_a(Article)
expect(response).to render_template :new
end
end

# ...
end
----

=== Subject

When several tests relate to the same subject, use `subject` to reduce repetition.

[source,ruby]
----
# bad
it { expect(hero.equipment).to be_heavy }
it { expect(hero.equipment).to include 'sword' }

# good
subject(:equipment) { hero.equipment }

it { expect(equipment).to be_heavy }
it { expect(equipment).to include 'sword' }
----

=== Named Subject [[use-subject]]

Use named `subject` when possible.
Only use anonymous subject declaration when you don't reference it in any tests, e.g. when `is_expected` is used.

[source,ruby]
----
# bad
describe Article do
subject { FactoryBot.create(:article) }

it 'is not published on creation' do
expect(subject).not_to be_published
end
end

# good
describe Article do
subject { FactoryBot.create(:article) }

it 'is not published on creation' do
is_expected.not_to be_published
end
end

# even better
describe Article do
subject(:article) { FactoryBot.create(:article) }

it 'is not published on creation' do
expect(article).not_to be_published
end
end
----

=== Subject Naming in Context

When you reassign subject with different attributes in different contexts, give different names to the subject, so it's easier to see what the actual subject represents.

[source,ruby]
----
# bad
describe Article do
context 'when there is an author' do
subject(:article) { FactoryBot.create(:article, author: user) }

it 'shows other articles by the same author' do
expect(article.related_stories).to include(story1, story2)
end
end

context 'when the author is anonymous' do
subject(:article) { FactoryBot.create(:article, author: nil) }

it 'matches stories by title' do
expect(article.related_stories).to include(story3, story4)
end
end
end

# good
describe Article do
context 'when article has an author' do
subject(:article) { FactoryBot.create(:article, author: user) }

it 'shows other articles by the same author' do
expect(article.related_stories).to include(story1, story2)
end
end

context 'when the author is anonymous' do
subject(:guest_article) { FactoryBot.create(:article, author: nil) }

it 'matches stories by title' do
expect(guest_article.related_stories).to include(story3, story4)
end
end
end
----

=== Don't Stub Subject

Don't stub methods of the object under test, it's a code smell and often indicates a bad design of the object itself.

[source,ruby]
----
# bad
describe 'Article' do
subject(:article) { Article.new }

it 'indicates that the author is unknown' do
allow(article).to receive(:author).and_return(nil)
expect(article.description).to include('by an unknown author')
end
end

# good - with correct subject initialization
describe 'Article' do
subject(:article) { Article.new(author: nil) }

it 'indicates that the author is unknown' do
expect(article.description).to include('by an unknown author')
end
end

# good - with better object design
describe 'Article' do
subject(:presenter) { ArticlePresenter.new(article) }
let(:article) { Article.new }

it 'indicates that the author is unknown' do
allow(article).to receive(:author).and_return(nil)
expect(presenter.description).to include('by an unknown author')
end
end
----

=== `it` and `specify`

Use `specify` if the example doesn't have a description, use `it` for examples with descriptions.
An exception is one-line example, where `it` is preferable.
`specify` is also useful when the docstring does not read well off of `it`.

[source,ruby]
----
# bad
it do
# ...
end

specify 'it sends an email' do
# ...
end

specify { is_expected.to be_truthy }

it '#do_something is deprecated' do
...
end

# good
specify do
# ...
end

it 'sends an email' do
# ...
end

it { is_expected.to be_truthy }

specify '#do_something is deprecated' do
...
end
----

=== `it` in Iterators

Do not write iterators to generate tests.
When another developer adds a feature to one of the items in the iteration, they must then break it out into a separate test - they are forced to edit code that has nothing to do with their pull request.

[source,ruby]
----
# bad
[:new, :show, :index].each do |action|
it 'returns 200' do
get action
expect(response).to be_ok
end
end

# good - more verbose, but better for the future development
describe 'GET new' do
it 'returns 200' do
get :new
expect(response).to be_ok
end
end

describe 'GET show' do
it 'returns 200' do
get :show
expect(response).to be_ok
end
end

describe 'GET index' do
it 'returns 200' do
get :index
expect(response).to be_ok
end
end
----

=== Incidental State

Avoid incidental state as much as possible.

[source,ruby]
----
# bad
it 'publishes the article' do
article.publish

# Creating another shared Article test object above would cause this
# test to break
expect(Article.count).to eq(2)
end

# good
it 'publishes the article' do
expect { article.publish }.to change(Article, :count).by(1)
end
----

=== DRY

Be careful not to focus on being 'DRY' by moving repeated expectations into a shared environment too early, as this can lead to brittle tests that rely too much on one another.

In general, it is best to start with doing everything directly in your `it` blocks even if it is duplication and then refactor your tests after you have them working to be a little more DRY.
However, keep in mind that duplication in test suites is NOT frowned upon, in fact it is preferred if it provides easier understanding and reading of a test.

=== Factories

Use https://github.com/thoughtbot/factory_bot[Factory Bot] to create test data in integration tests.
You should very rarely have to use `ModelName.create` within an integration spec.
Do *not* use fixtures as they are not nearly as maintainable as factories.

[source,ruby]
----
# bad
subject(:article) do
Article.create(
title: 'Piccolina',
author: 'John Archer',
published_at: '17 August 2172',
approved: true
)
end

# good
subject(:article) { FactoryBot.create(:article) }
----

NOTE: When talking about unit tests the best practice would be to use neither fixtures nor factories.
Put as much of your domain logic in libraries that can be tested without needing complex, time consuming setup with either factories or fixtures.

=== Needed Data

Do not load more data than needed to test your code.

[source,ruby]
----
# good
RSpec.describe User do
describe ".top" do
subject { described_class.top(2) }

before { FactoryBot.create_list(:user, 3) }

it { is_expected.to have(2).items }
end
end
----

=== Doubles

Prefer using verifying doubles over normal doubles.

Verifying doubles are a stricter alternative to normal doubles that provide guarantees, e.g. a failure will be triggered if an invalid method is being stubbed or a method is called with an invalid number of arguments.

In general, use doubles with more isolated/behavioral tests rather than with integration tests.

NOTE: There is no justification for turning `verify_partial_doubles` configuration option off.
That will significantly reduce the confidence in partial doubles.

[source,ruby]
----
# good - verifying instance double
article = instance_double('Article')
allow(article).to receive(:author).and_return(nil)

presenter = described_class.new(article)
expect(presenter.title).to include('by an unknown author')

# good - verifying object double
article = object_double(Article.new, valid?: true)
expect(article.save).to be true

# good - verifying partial double
allow(Article).to receive(:find).with(5).and_return(article)

# good - verifying class double
notifier = class_double('Notifier')
expect(notifier).to receive(:notify).with('suspended as')
----

NOTE: If you stub a method that could give a false-positive test result, you have gone too far.

=== Dealing with Time

Always use https://github.com/travisjeffery/timecop[Timecop] instead of stubbing anything on Time or Date.

[source,ruby]
----
describe InvoiceReminder do
subject(:time_with_offset) { described_class.new.get_offset_time }

# bad
it 'offsets the time 2 days into the future' do
current_time = Time.now
allow(Time).to receive(:now).and_return(current_time)
expect(time_with_offset).to eq(current_time + 2.days)
end

# good
it 'offsets the time 2 days into the future' do
Timecop.freeze(Time.now) do
expect(time_with_offset).to eq 2.days.from_now
end
end
end
----

=== Stub HTTP Requests

Stub HTTP requests when the code is making them.
Avoid hitting real external services.

Use https://github.com/bblimke/webmock[webmock] and https://github.com/vcr/vcr[VCR] separately or https://marnen.github.io/webmock-presentation/webmock.html[together].

[source,ruby]
----
# good
context 'with unauthorized access' do
let(:uri) { 'http://api.lelylan.com/types' }

before { stub_request(:get, uri).to_return(status: 401, body: fixture('401.json')) }

it 'returns access denied' do
page.driver.get uri
expect(page).to have_content 'Access denied'
end
end
----

[#declare-constants]
=== Declare Constants

Do not explicitly declare classes, modules, or constants in example groups.
https://rspec.info/features/3-12/rspec-mocks/mutating-constants/[Stub constants instead].

NOTE: Constants, including classes and modules, when declared in a block scope, are defined in global namespace, and leak between examples.

[source,ruby]
----
# bad
describe SomeClass do
CONSTANT_HERE = 'I leak into global namespace'
end

# good
describe SomeClass do
before do
stub_const('CONSTANT_HERE', 'I only exist during this example')
end
end

# bad
describe SomeClass do
class FooClass < described_class
def double_that
some_base_method * 2
end
end

it { expect(FooClass.new.double_that).to eq(4) }
end

# good - anonymous class, no constant needs to be defined
describe SomeClass do
let(:foo_class) do
Class.new(described_class) do
def double_that
some_base_method * 2
end
end
end

it { expect(foo_class.new.double_that).to eq(4) }
end

# good - constant is stubbed
describe SomeClass do
before do
foo_class = Class.new(described_class) do
def do_something
end
end
stub_const('FooClass', foo_class)
end

it { expect(FooClass.new.double_that).to eq(4) }
end
----

[#implicit-block-expectations]
=== Implicit Block Expectations

Avoid using implicit block expectations.

[source,ruby]
----
# bad
subject { -> { do_something } }
it { is_expected.to change(something).to(new_value) }

# good
it 'changes something to a new value' do
expect { do_something }.to change(something).to(new_value)
end
----

== Naming

=== Context Descriptions

Context descriptions should describe the conditions shared by all the examples within. Full example names (formed by concatenation of all nested block descriptions) should form a readable sentence.

A typical description will be an adjunct phrase starting with 'when', 'with', 'without', or similar words.

[source,ruby]
----
# bad - 'Summary user logged in no display name shows a placeholder'
describe 'Summary' do
context 'user logged in' do
context 'no display name' do
it 'shows a placeholder' do
end
end
end
end

# good - 'Summary when the user is logged in when the display name is blank shows a placeholder'
describe 'Summary' do
context 'when the user is logged in' do
context 'when the display name is blank' do
it 'shows a placeholder' do
end
end
end
end
----

=== Example Descriptions

`it`/`specify` block descriptions should never end with a conditional.
This is a code smell that the `it` most likely needs to be wrapped in a `context`.

[source,ruby]
----
# bad
it 'returns the display name if it is present' do
# ...
end

# good
context 'when display name is present' do
it 'returns the display name' do
# ...
end
end

# This encourages the addition of negative test cases that might have
# been overlooked
context 'when display name is not present' do
it 'returns nil' do
# ...
end
end
----

=== Keep Example Descriptions Short

Keep example description shorter than 60 characters.

Write the example that documents itself, and generates proper
documentation format output.

[source,ruby]
----
# bad
it 'rewrites "should not return something" as "does not return something"' do
# ...
end

# good
it 'rewrites "should not return something"' do
expect(rewrite('should not return something')).to
eq 'does not return something'
end

# good - self-documenting
specify do
expect(rewrite('should not return something')).to
eq 'does not return something'
end
----

=== "Should" in Example Docstrings[[should-in-it]]

Do not write 'should' or 'should not' in the beginning of your example docstrings.
The descriptions represent actual functionality, not what might be happening.
Use the third person in the present tense.

[source,ruby]
----
# bad
it 'should return the summary' do
# ...
end

# good
it 'returns the summary' do
# ...
end
----

=== Describe the Methods[[example-group-naming]]

Be clear about what method you are describing.
Use the Ruby documentation convention of `.` when referring to a class method's name and `#` when referring to an instance method's name.

[source,ruby]
----
# bad
describe 'the authenticate method for User' do
# ...
end

describe 'if the user is an admin' do
# ...
end

# good
describe '.authenticate' do
# ...
end

describe '#admin?' do
# ...
end
----

=== Use `expect`

Always use the newer `expect` syntax.

Configure RSpec to only accept the new `expect` syntax.

[source,ruby]
----
# bad
it 'creates a resource' do
response.should respond_with_content_type(:json)
end

# good
it 'creates a resource' do
expect(response).to respond_with_content_type(:json)
end
----

== Matchers

=== Predicate Matchers

Use RSpec's predicate matcher methods when possible.

[source,ruby]
----
describe Article do
subject(:article) { FactoryBot.create(:article) }

# bad
it 'is published' do
expect(article.published?).to be true
end

# good
it 'is published' do
expect(article).to be_published
end

# even better
it { is_expected.to be_published }
end
----

=== Built in Matchers

Use built-in matchers.

[source,ruby]
----
# bad
it 'includes a title' do
expect(article.title.include?('a lengthy title')).to be true
end

# good
it 'includes a title' do
expect(article.title).to include 'a lengthy title'
end
----

=== `be` Matcher

Avoid using `be` matcher without arguments.
It is too generic, as it pass on everything that is not `nil` or `false`.
If that is the exact intent, use `be_truthy`.
In all other cases it's better to specify what exactly is the expected value.

[source,ruby]
----
# bad
it 'has author' do
expect(article.author).to be
end

# good
it 'has author' do
expect(article.author).to be_truthy # same as the original
expect(article.author).not_to be_nil # `be` is often used to check for non-nil value
expect(article.author).to be_an(Author) # explicit check for the type of the value
end
----

=== Extract Common Expectation Parts into Matchers

Extract frequently used common logic from your examples into https://rspec.info/features/3-12/rspec-expectations/custom-matchers/define-matcher/[custom matchers].

[source,ruby]
----
# bad
it 'returns JSON with temperature in Celsius' do
json = JSON.parse(response.body).with_indifferent_access
expect(json[:celsius]).to eq 30
end

it 'returns JSON with temperature in Fahrenheit' do
json = JSON.parse(response.body).with_indifferent_access
expect(json[:fahrenheit]).to eq 86
end

# good
it 'returns JSON with temperature in Celsius' do
expect(response).to include_json(celsius: 30)
end

it 'returns JSON with temperature in Fahrenheit' do
expect(response).to include_json(fahrenheit: 86)
end
----

=== `any_instance_of`

Avoid using `allow_any_instance_of`/`expect_any_instance_of`.
It might be an indication that the object under test is too complex, and is ambiguous when used with receive counts.

[source,ruby]
----
# bad
it 'has a name' do
allow_any_instance_of(User).to receive(:name).and_return('Tweedledee')
expect(account.name).to eq 'Tweedledee'
end

# good
let(:account) { Account.new(user) }

it 'has a name' do
allow(user).to receive(:name).and_return('Tweedledee')
expect(account.name).to eq 'Tweedledee'
end
----

=== Matcher Libraries

Use third-party matcher libraries that provide convenience helpers that will significantly simplify the examples, https://github.com/thoughtbot/shoulda-matchers[Shoulda Matchers] are one worth mentioning.

[source,ruby]
----
# bad
describe '#title' do
it 'is required' do
article.title = nil
article.valid?
expect(article.errors[:title])
.to contain_exactly('Article has no title')
not
end
end

# good
describe '#title' do
it 'is required' do
expect(article).to validate_presence_of(:title)
.with_message('Article has no title')
end
end
----

== Rails: Integration[[integration]][[rails]]

Test what you see.
Deeply test your models and your application behaviour (integration tests).
Do not add useless complexity testing controllers.

This is an open debate in the Ruby community and both sides have good arguments supporting their idea.
People supporting the need of testing controllers will tell you that your integration tests don't cover all use cases and that they are slow.
Both are wrong.
It is possible to cover all use cases and it's possible to make them fast.

== Rails: Views[[views]]

=== View Directory Structure

The directory structure of the view specs `spec/views` matches the one in `app/views`.
For example the specs for the views in `app/views/users` are placed in `spec/views/users`.

=== View Spec File Name

The naming convention for the view specs is adding `_spec.rb` to the view name, for example the view `_form.html.erb` has a corresponding spec `_form.html.erb_spec.rb`.

=== View Outer `describe`

The outer `describe` block uses the path to the view without the `app/views` part.
This is used by the `render` method when it is called without arguments.

[source,ruby]
----
# spec/views/articles/new.html.erb_spec.rb
describe 'articles/new.html.erb' do
# ...
end
----

=== View Mock Models

Always mock the models in the view specs.
The purpose of the view is only to display information.

=== View `assign`

The method `assign` supplies the instance variables which the view uses and are supplied by the controller.

[source,ruby]
----
# spec/views/articles/edit.html.erb_spec.rb
describe 'articles/edit.html.erb' do
it 'renders the form for a new article creation' do
assign(:article, double(Article).as_null_object)
render
expect(rendered).to have_selector('form',
method: 'post',
action: articles_path
) do |form|
expect(form).to have_selector('input', type: 'submit')
end
end
end
----

=== Capybara Negative Selectors[[view-capybara-negative-selectors]]

Prefer capybara negative selectors over `to_not` with positive ones.

[source,ruby]
----
# bad
expect(page).to_not have_selector('input', type: 'submit')
expect(page).to_not have_xpath('tr')

# good
expect(page).to have_no_selector('input', type: 'submit')
expect(page).to have_no_xpath('tr')
----

=== View Helper Stub

When a view uses helper methods, these methods need to be stubbed.
Stubbing the helper methods is done on the `template` object:

[source,ruby]
----
# app/helpers/articles_helper.rb
class ArticlesHelper
def formatted_date(date)
# ...
end
end
----

[source,ruby]
----
# app/views/articles/show.html.erb
<%= 'Published at: #{formatted_date(@article.published_at)}' %>
----

[source,ruby]
----
# spec/views/articles/show.html.erb_spec.rb
describe 'articles/show.html.erb' do
it 'displays the formatted date of article publishing' do
article = double(Article, published_at: Date.new(2012, 01, 01))
assign(:article, article)

allow(template).to_receive(:formatted_date).with(article.published_at).and_return('01.01.2012')

render
expect(rendered).to have_content('Published at: 01.01.2012')
end
end
----

=== View Helpers

The helpers specs are separated from the view specs in the `spec/helpers` directory.

== Rails: Controllers[[controllers]]

=== Controller Models

Mock the models and stub their methods.
Testing the controller should not depend on the model creation.

=== Controller Behaviour

Test only the behaviour the controller should be responsible about:

* Execution of particular methods
* Data returned from the action - assigns, etc.
* Result from the action - template render, redirect, etc.

[source,ruby]
----
# Example of a commonly used controller spec
# spec/controllers/articles_controller_spec.rb
# We are interested only in the actions the controller should perform
# So we are mocking the model creation and stubbing its methods
# And we concentrate only on the things the controller should do

describe ArticlesController do
# The model will be used in the specs for all methods of the controller
let(:article) { double(Article) }

describe 'POST create' do
before { allow(Article).to receive(:new).and_return(article) }

it 'creates a new article with the given attributes' do
expect(Article).to receive(:new).with(title: 'The New Article Title').and_return(article)
post :create, message: { title: 'The New Article Title' }
end

it 'saves the article' do
expect(article).to receive(:save)
post :create
end

it 'redirects to the Articles index' do
allow(article).to receive(:save)
post :create
expect(response).to redirect_to(action: 'index')
end
end
end
----

=== Controller Contexts

Use context when the controller action has different behaviour depending on the received params.

[source,ruby]
----
# A classic example for use of contexts in a controller spec is creation or update when the object saves successfully or not.

describe ArticlesController do
let(:article) { double(Article) }

describe 'POST create' do
before { allow(Article).to receive(:new).and_return(article) }

it 'creates a new article with the given attributes' do
expect(Article).to receive(:new).with(title: 'The New Article Title').and_return(article)
post :create, article: { title: 'The New Article Title' }
end

it 'saves the article' do
expect(article).to receive(:save)
post :create
end

context 'when the article saves successfully' do
before do
allow(article).to receive(:save).and_return(true)
end

it 'sets a flash[:notice] message' do
post :create
expect(flash[:notice]).to eq('The article was saved successfully.')
end

it 'redirects to the Articles index' do
post :create
expect(response).to redirect_to(action: 'index')
end
end

context 'when the article fails to save' do
before do
allow(article).to receive(:save).and_return(false)
end

it 'assigns @article' do
post :create
expect(assigns[:article]).to eq(article)
end

it "re-renders the 'new' template" do
post :create
expect(response).to render_template('new')
end
end
end
end
----

== Rails: Models[[models]]

=== Model Mocks

Do not mock the models in their own specs.

=== Model Objects

Use `FactoryBot.create` to make real objects, or just use a new (unsaved) instance with `subject`.

[source,ruby]
----
describe Article do
subject(:article) { FactoryBot.create(:article) }

it { is_expected.to be_an Article }
it { is_expected.to be_persisted }
end
----

=== Model Mock Associations

It is acceptable to mock other models or child objects.

=== Avoid Duplication in Model Tests[[model-avoid-duplication]]

Create the model for all examples in the spec to avoid duplication.

[source,ruby]
----
describe Article do
let(:article) { FactoryBot.create(:article) }
end
----

=== Check Model Validity[[model-check-validity]]

Add an example ensuring that the model created with `FactoryBot.create` is valid.

[source,ruby]
----
describe Article do
it 'is valid with valid attributes' do
expect(article).to be_valid
end
end
----

=== Model Validations

When testing validations, use `expect(model.errors[:attribute].size).to eq(x)` to specify the attribute which should be validated.
Using `be_valid` does not guarantee that the problem is in the intended attribute.

[source,ruby]
----
# bad
describe '#title' do
it 'is required' do
article.title = nil
expect(article).to_not be_valid
end
end

# preferred
describe '#title' do
it 'is required' do
article.title = nil
article.valid?
expect(article.errors[:title].size).to eq(1)
end
end
----

=== Separate Example Group for Attribute Validations[[model-separate-describe-for-attribute-validations]]

Add a separate `describe` for each attribute which has validations.

[source,ruby]
----
describe '#title' do
it 'is required' do
article.title = nil
article.valid?
expect(article.errors[:title].size).to eq(1)
end
end

describe '#name' do
it 'is required' do
article.name = nil
article.valid?
expect(article.errors[:name].size).to eq(1)
end
end
----

=== Naming Another Object[[model-name-another-object]]

When testing uniqueness of a model attribute, name the other object `another_object`.

[source,ruby]
----
describe Article do
describe '#title' do
it 'is unique' do
another_article = FactoryBot.create(:article, title: article.title)
article.valid?
expect(article.errors[:title].size).to eq(1)
end
end
end
----

== Rails: Mailers[[mailers]]

=== Mailer Mock Model

The model in the mailer spec should be mocked.
The mailer should not depend on the model creation.

=== Mailer Expectations

The mailer spec should verify that:

* the subject is correct
* the sender e-mail is correct
* the e-mail is sent to the correct recipient
* the e-mail contains the required information

[source,ruby]
----
describe SubscriberMailer do
let(:subscriber) { double(Subscription, email: '[email protected]', name: 'John Doe') }

describe 'successful registration email' do
subject(:email) { SubscriptionMailer.successful_registration_email(subscriber) }

it { is_expected.to have_attributes(subject: 'Successful Registration!', from: ['infor@your_site.com'], to: [subscriber.email]) }

it 'contains the subscriber name' do
expect(email.body.encoded).to match(subscriber.name)
end
end
end
----

== Recommendations

=== Correct Setup

Correctly set up RSpec configuration globally (`~/.rspec`), per project (`.rspec`), and in project override file that is supposed to be kept out of version control (`.rspec-local`).
Use `rspec --init` to generate `.rspec` and `spec/spec_helper.rb` files.

----
# .rspec
--color
--require spec_helper

# .rspec-local
--profile 2
----

== Related Guides

* https://rubystyle.guide[Ruby Style Guide]
* https://rails.rubystyle.guide[Rails Style Guide]
* https://minitest.rubystyle.guide[Minitest Style Guide]

== Contributing

Nothing written in this guide is set in stone.
Everyone is welcome to contribute, so that we could ultimately create a resource that will be beneficial to the entire Ruby community.

Feel free to open tickets or send pull requests with improvements.
Thanks in advance for your help!

You can also support the project (and RuboCop) with financial contributions via https://www.patreon.com/bbatsov[Patreon].

=== How to Contribute?

It's easy, just follow the contribution guidelines below:

* https://docs.github.com/en/get-started/quickstart/fork-a-repo[Fork] the https://github.com/rubocop/rspec-style-guide[project] on GitHub
* Make your feature addition or bug fix in a feature branch
* Include a http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[good description] of your changes
* Push your feature branch to GitHub
* Send a https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests[Pull Request]

== License

image:https://i.creativecommons.org/l/by/3.0/88x31.png[Creative Commons License]
This work is licensed under a http://creativecommons.org/licenses/by/3.0/deed.en_US[Creative Commons Attribution 3.0 Unported License]

== Credit

Inspiration was taken from the following:

https://github.com/howaboutwe/rspec-style-guide[HowAboutWe's RSpec style guide]

https://github.com/rubocop/rails-style-guide[Community Rails style guide]

This guide was maintained by https://github.com/reachlocal[ReachLocal] for a long while.

This guide includes material originally present in https://github.com/betterspecs/betterspecs[BetterSpecs] (https://betterspecs.github.io/betterspecs/[newer site] https://www.betterspecs.org/[older site]), sponsored by https://github.com/lelylan[Lelylan] and maintained by https://github.com/andreareginato[Andrea Reginato] and https://github.com/betterspecs/betterspecs/graphs/contributors[many others] for a long while.