Skip to content

rubocop/rspec-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RSpec Style Guide

Role models are important.

— Officer Alex J. Murphy / RoboCop
Tip
You can find a beautiful version of this guide with much improved navigation at https://rspec.rubystyle.guide.

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.

RuboCop, a static code analyzer (linter) and formatter, has a 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 AsciiDoctor PDF, and an HTML copy with AsciiDoctor using the following commands:

# 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.

gem install rouge

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.

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.

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.

# 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

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

# 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

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

# 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

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

# 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

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

# 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

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

# 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

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

  • subject

  • let!/let

  • before/after

# 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 to make the tests clear, well organized, and easy to read.

# 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 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.

# 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

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 lets for primitive data, find the balance between frequency of use and complexity of the definition.

# 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

Use let definitions instead of instance variables.

# 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

Use shared examples to reduce code duplication.

# 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

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

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

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

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

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

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.

# 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

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

# 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' }

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.

# 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

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.

# 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 methods of the object under test, it’s a code smell and often indicates a bad design of the object itself.

# 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

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.

# 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

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.

# 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

Avoid incidental state as much as possible.

# 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

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.

Use 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.

# 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.

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

# 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

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.
# 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.

Always use Timecop instead of stubbing anything on Time or Date.

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 when the code is making them. Avoid hitting real external services.

Use webmock and VCR separately or together.

# 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

Do not explicitly declare classes, modules, or constants in example groups. Stub constants instead.

Note
Constants, including classes and modules, when declared in a block scope, are defined in global namespace, and leak between examples.
# 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

Avoid using implicit block expectations.

# 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

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.

# 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

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.

# 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 description shorter than 60 characters.

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

# 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

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.

# bad it 'should return the summary' do # ... end # good it 'returns the summary' do # ... end

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.

# 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

Always use the newer expect syntax.

Configure RSpec to only accept the new expect syntax.

# 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

Use RSpec’s predicate matcher methods when possible.

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

Use built-in matchers.

# 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

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.

# 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 frequently used common logic from your examples into custom matchers.

# 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

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.

# 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

Use third-party matcher libraries that provide convenience helpers that will significantly simplify the examples, Shoulda Matchers are one worth mentioning.

# 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

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.

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.

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.

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.

# spec/views/articles/new.html.erb_spec.rb describe 'articles/new.html.erb' do # ... end

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

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

# 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

Prefer capybara negative selectors over to_not with positive ones.

# 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')

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

# app/helpers/articles_helper.rb class ArticlesHelper def formatted_date(date) # ... end end
# app/views/articles/show.html.erb <%= 'Published at: #{formatted_date(@article.published_at)}' %>
# 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

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

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

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.

# 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

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

# 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

Do not mock the models in their own specs.

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

describe Article do subject(:article) { FactoryBot.create(:article) } it { is_expected.to be_an Article } it { is_expected.to be_persisted } end

It is acceptable to mock other models or child objects.

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

describe Article do let(:article) { FactoryBot.create(:article) } end

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

describe Article do it 'is valid with valid attributes' do expect(article).to be_valid end end

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.

# 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

Add a separate describe for each attribute which has validations.

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

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

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

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

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

describe SubscriberMailer do let(:subscriber) { double(Subscription, email: 'johndoe@test.com', 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

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

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 Patreon.

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

Inspiration was taken from the following:

This guide was maintained by ReachLocal for a long while.

This guide includes material originally present in BetterSpecs (newer site older site), sponsored by Lelylan and maintained by Andrea Reginato and many others for a long while.

About

Best practices for writing your specs!

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 35