{"id":13652363,"url":"https://github.com/rubocop/rspec-style-guide","last_synced_at":"2025-05-16T07:06:46.720Z","repository":{"id":37496421,"uuid":"8885599","full_name":"rubocop/rspec-style-guide","owner":"rubocop","description":"Best practices for writing your specs!","archived":false,"fork":false,"pushed_at":"2023-12-11T13:00:36.000Z","size":424,"stargazers_count":958,"open_issues_count":20,"forks_count":118,"subscribers_count":153,"default_branch":"master","last_synced_at":"2024-10-29T17:31:49.800Z","etag":null,"topics":["best-practices","rspec","ruby","style","style-guide"],"latest_commit_sha":null,"homepage":"http://rspec.rubystyle.guide","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rubocop.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2013-03-19T18:12:19.000Z","updated_at":"2024-10-22T16:24:29.000Z","dependencies_parsed_at":"2023-12-11T13:50:53.291Z","dependency_job_id":null,"html_url":"https://github.com/rubocop/rspec-style-guide","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frspec-style-guide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frspec-style-guide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frspec-style-guide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frspec-style-guide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rubocop","download_url":"https://codeload.github.com/rubocop/rspec-style-guide/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254485066,"owners_count":22078767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["best-practices","rspec","ruby","style","style-guide"],"created_at":"2024-08-02T02:00:58.652Z","updated_at":"2025-05-16T07:06:41.690Z","avatar_url":"https://github.com/rubocop.png","language":null,"funding_links":["https://www.patreon.com/bbatsov"],"categories":["Frameworks","Others"],"sub_categories":["RSpec"],"readme":"= RSpec Style Guide\n:idprefix:\n:idseparator: -\n:sectanchors:\n:sectlinks:\n:toc: preamble\n:toclevels: 1\nifndef::backend-pdf[]\n:toc-title: pass:[\u003ch2\u003eTable of Contents\u003c/h2\u003e]\nendif::[]\n:source-highlighter: rouge\n\n== Introduction\n\n[quote, Officer Alex J. Murphy / RoboCop]\n____\nRole models are important.\n____\n\nifdef::env-github[]\nTIP: You can find a beautiful version of this guide with much improved navigation at https://rspec.rubystyle.guide.\nendif::[]\n\nThis RSpec style guide outlines the recommended best practices for real-world programmers to write code that can be maintained by other real-world programmers.\n\nhttps://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.\n\nNOTE: This guide assumes you are using RSpec 3 or later.\n\nYou 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:\n\n[source,shell]\n----\n# Generates README.pdf\nasciidoctor-pdf -a allow-uri-read README.adoc\n\n# Generates README.html\nasciidoctor README.adoc\n----\n\n[TIP]\n====\nInstall the `rouge` gem to get nice syntax highlighting in the generated document.\n\n[source,shell]\n----\ngem install rouge\n----\n====\n\n== How to Read This Guide\n\nThe 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.\n\n== A Living Document\n\nPer 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.\nWith that said, as the standard changes this guide is meant to be able to change with it.\n\n== Layout\n\n=== Empty Lines inside Example Group[[empty-lines-after-describe]]\n\nDo not leave empty lines after `feature`, `context` or `describe` descriptions.\nIt doesn't make the code more readable and lowers the value of logical chunks.\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n\n  describe '#summary' do\n\n    context 'when there is a summary' do\n\n      it 'returns the summary' do\n        # ...\n      end\n    end\n  end\nend\n\n# good\ndescribe Article do\n  describe '#summary' do\n    context 'when there is a summary' do\n      it 'returns the summary' do\n        # ...\n      end\n    end\n  end\nend\n----\n\n=== Empty Line between Example Groups [[empty-lines-between-describes]]\n\nLeave one empty line between `feature`, `context` or `describe` blocks.\nDo not leave empty line after the last such block in a group.\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n  describe '#summary' do\n    context 'when there is a summary' do\n      # ...\n    end\n    context 'when there is no summary' do\n      # ...\n    end\n\n  end\n  describe '#comments' do\n    # ...\n  end\nend\n\n# good\ndescribe Article do\n  describe '#summary' do\n    context 'when there is a summary' do\n      # ...\n    end\n\n    context 'when there is no summary' do\n      # ...\n    end\n  end\n\n  describe '#comments' do\n    # ...\n  end\nend\n----\n\n=== Empty Line After `let`[[empty-lines-after-let]]\n\nLeave one empty line after `let`, `subject`, and `before`/`after` blocks.\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n  subject { FactoryBot.create(:some_article) }\n  describe '#summary' do\n    # ...\n  end\nend\n\n# good\ndescribe Article do\n  subject { FactoryBot.create(:some_article) }\n\n  describe '#summary' do\n    # ...\n  end\nend\n----\n\n=== Let Grouping\n\nOnly group `let`, `subject` blocks and separate them from `before`/`after` blocks.\nIt makes the code much more readable.\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n  subject { FactoryBot.create(:some_article) }\n  let(:user) { FactoryBot.create(:user) }\n  before do\n    # ...\n  end\n  after do\n    # ...\n  end\n  describe '#summary' do\n    # ...\n  end\nend\n\n# good\ndescribe Article do\n  subject { FactoryBot.create(:some_article) }\n  let(:user) { FactoryBot.create(:user) }\n\n  before do\n    # ...\n  end\n\n  after do\n    # ...\n  end\n\n  describe '#summary' do\n    # ...\n  end\nend\n----\n\n=== Empty Lines around Examples[[empty-lines-around-it]]\n\nLeave one empty line around `it`/`specify` blocks. This helps to separate the expectations from their conditional logic (contexts for instance).\n\n[source,ruby]\n----\n# bad\ndescribe '#summary' do\n  let(:item) { double('something') }\n\n  it 'returns the summary' do\n    # ...\n  end\n  it 'does something else' do\n    # ...\n  end\n  it 'does another thing' do\n    # ...\n  end\nend\n\n# good\ndescribe '#summary' do\n  let(:item) { double('something') }\n\n  it 'returns the summary' do\n    # ...\n  end\n\n  it 'does something else' do\n    # ...\n  end\n\n  it 'does another thing' do\n    # ...\n  end\nend\n----\n\n== Example Group Structure\n\n=== Leading `subject`\n\nWhen `subject` is used, it should be the first declaration in the example group.\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n  before do\n    # ...\n  end\n\n  let(:user) { FactoryBot.create(:user) }\n  subject { FactoryBot.create(:some_article) }\n\n  describe '#summary' do\n    # ...\n  end\nend\n\n# good\ndescribe Article do\n  subject { FactoryBot.create(:some_article) }\n  let(:user) { FactoryBot.create(:user) }\n\n  before do\n    # ...\n  end\n\n  describe '#summary' do\n    # ...\n  end\nend\n----\n\n=== Declaring `subject`, `let!`/`let` and `before`/`after` hooks\n\nWhen declaring `subject`, `let!`/`let` and `before`/`after` hooks they should be in the following order:\n\n* `subject`\n* `let!`/`let`\n* `before`/`after`\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n  before do\n    # ...\n  end\n\n  after do\n    # ...\n  end\n\n  let(:user) { FactoryBot.create(:user) }\n  subject { FactoryBot.create(:some_article) }\n\n  describe '#summary' do\n    # ...\n  end\nend\n\n# good\ndescribe Article do\n  subject { FactoryBot.create(:some_article) }\n  let(:user) { FactoryBot.create(:user) }\n\n  before do\n    # ...\n  end\n\n  after do\n    # ...\n  end\n\n  describe '#summary' do\n    # ...\n  end\nend\n----\n\n=== Use Contexts\n\nUse contexts to make the tests clear, well organized, and easy to read.\n\n[source,ruby]\n----\n# bad\nit 'has 200 status code if logged in' do\n  expect(response).to respond_with 200\nend\n\nit 'has 401 status code if not logged in' do\n  expect(response).to respond_with 401\nend\n\n# good\ncontext 'when logged in' do\n  it { is_expected.to respond_with 200 }\nend\n\ncontext 'when logged out' do\n  it { is_expected.to respond_with 401 }\nend\n----\n\n=== Context Cases\n\n`context` blocks should pretty much always have an opposite negative case.\nIt 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.\n\n[source,ruby]\n----\n# bad - needs refactoring\ndescribe '#attributes' do\n  context 'the returned hash' do\n    it 'includes the display name' do\n      # ...\n    end\n\n    it 'includes the creation time' do\n      # ...\n    end\n  end\nend\n\n# bad - the negative case needs to be tested, but isn't\ndescribe '#attributes' do\n  context 'when display name is present' do\n    before do\n      article.display_name = 'something'\n    end\n\n    it 'includes the display name' do\n      # ...\n    end\n  end\nend\n\n# good\ndescribe '#attributes' do\n  subject(:attributes) { article.attributes }\n  let(:article) { FactoryBot.create(:article) }\n\n  context 'when display name is present' do\n    before do\n      article.display_name = 'something'\n    end\n\n    it { is_expected.to include(display_name: article.display_name) }\n  end\n\n  context 'when display name is not present' do\n    before do\n      article.display_name = nil\n    end\n\n    it { is_expected.not_to include(:display_name) }\n  end\nend\n----\n\n=== `let` Blocks\n\nUse `let` and `let!` for data that is used across several examples in an example group.\nUse `let!` to define variables even if they are not referenced in some of the examples, e.g. when testing balancing negative cases.\nDo not overuse ``let``s for primitive data, find the balance between frequency of use and complexity of the definition.\n\n[source,ruby]\n----\n# bad\nit 'finds shortest path' do\n  tree = Tree.new(1 =\u003e 2, 2 =\u003e 3, 2 =\u003e 6, 3 =\u003e 4, 4 =\u003e 5, 5 =\u003e 6)\n  expect(dijkstra.shortest_path(tree, from: 1, to: 6)).to eq([1, 2, 6])\nend\n\nit 'finds longest path' do\n  tree = Tree.new(1 =\u003e 2, 2 =\u003e 3, 2 =\u003e 6, 3 =\u003e 4, 4 =\u003e 5, 5 =\u003e 6)\n  expect(dijkstra.longest_path(tree, from: 1, to: 6)).to eq([1, 2, 3, 4, 5, 6])\nend\n\n# good\nlet(:tree) { Tree.new(1 =\u003e 2, 2 =\u003e 3, 2 =\u003e 6, 3 =\u003e 4, 4 =\u003e 5, 5 =\u003e 6) }\n\nit 'finds shortest path' do\n  expect(dijkstra.shortest_path(tree, from: 1, to: 6)).to eq([1, 2, 6])\nend\n\nit 'finds longest path' do\n  expect(dijkstra.longest_path(tree, from: 1, to: 6)).to eq([1, 2, 3, 4, 5, 6])\nend\n----\n\n=== Instance Variables\n\nUse `let` definitions instead of instance variables.\n\n[source,ruby]\n----\n# bad\nbefore { @name = 'John Wayne' }\n\nit 'reverses a name' do\n  expect(reverser.reverse(@name)).to eq('enyaW nhoJ')\nend\n\n# good\nlet(:name) { 'John Wayne' }\n\nit 'reverses a name' do\n  expect(reverser.reverse(name)).to eq('enyaW nhoJ')\nend\n----\n\n=== Shared Examples\n\nUse shared examples to reduce code duplication.\n\n[source,ruby]\n----\n# bad\ndescribe 'GET /articles' do\n  let(:article) { FactoryBot.create(:article, owner: owner) }\n\n  before { page.driver.get '/articles' }\n\n  context 'when user is the owner' do\n    let(:user) { owner }\n\n    it 'shows all owned articles' do\n      expect(page.status_code).to be(200)\n      contains_resource resource\n    end\n  end\n\n  context 'when user is an admin' do\n    let(:user) { FactoryBot.create(:user, :admin) }\n\n    it 'shows all resources' do\n      expect(page.status_code).to be(200)\n      contains_resource resource\n    end\n  end\nend\n\n# good\ndescribe 'GET /articles' do\n  let(:article) { FactoryBot.create(:article, owner: owner) }\n\n  before { page.driver.get '/articles' }\n\n  shared_examples 'shows articles' do\n    it 'shows all related articles' do\n      expect(page.status_code).to be(200)\n      contains_resource resource\n    end\n  end\n\n  context 'when user is the owner' do\n    let(:user) { owner }\n\n    include_examples 'shows articles'\n  end\n\n  context 'when user is an admin' do\n    let(:user) { FactoryBot.create(:user, :admin) }\n\n    include_examples 'shows articles'\n  end\nend\n\n# good\ndescribe 'GET /devices' do\n  let(:resource) { FactoryBot.create(:device, created_from: user) }\n\n  it_behaves_like 'a listable resource'\n  it_behaves_like 'a paginable resource'\n  it_behaves_like 'a searchable resource'\n  it_behaves_like 'a filterable list'\nend\n----\n\n=== Redundant `before(:each)`\n\nDon't specify `:each`/`:example` scope for `before`/`after`/`around` blocks, as it is the default.\nPrefer `:example` when explicitly indicating the scope.\n\n[source,ruby]\n----\n# bad\ndescribe '#summary' do\n  before(:example) do\n    # ...\n  end\n\n  # ...\nend\n\n# good\ndescribe '#summary' do\n  before do\n    # ...\n  end\n\n  # ...\nend\n----\n\n=== Ambiguous Hook Scope\n\nUse `:context` instead of the ambiguous `:all` scope in `before`/`after` hooks.\n\n[source,ruby]\n----\n# bad\ndescribe '#summary' do\n  before(:all) do\n    # ...\n  end\n\n  # ...\nend\n\n# good\ndescribe '#summary' do\n  before(:context) do\n    # ...\n  end\n\n  # ...\nend\n----\n\n=== Avoid Hooks with `:context` Scope\n\nAvoid using `before`/`after` with `:context` scope.\nBeware of the state leakage between the examples.\n\n== Example Structure\n\n=== Expectation per Example[[one-expectation]]\n\nFor examples two styles are considered acceptable.\nThe first variant is separate example for each expectation, which comes with a cost of repeated context initialization.\nThe second variant is multiple expectations per example with `aggregate_failures` tag set for a group or example.\nUse your best judgement in each case, and apply your strategy consistently.\n\n[source,ruby]\n----\n# good - one expectation per example\ndescribe ArticlesController do\n  #...\n\n  describe 'GET new' do\n    it 'assigns a new article' do\n      get :new\n      expect(assigns[:article]).to be_a(Article)\n    end\n\n    it 'renders the new article template' do\n      get :new\n      expect(response).to render_template :new\n    end\n  end\nend\n\n# good - multiple expectations with aggregated failures\ndescribe ArticlesController do\n  #...\n\n  describe 'GET new', :aggregate_failures do\n    it 'assigns new article and renders the new article template' do\n      get :new\n      expect(assigns[:article]).to be_a(Article)\n      expect(response).to render_template :new\n    end\n  end\n\n  # ...\nend\n----\n\n=== Subject\n\nWhen several tests relate to the same subject, use `subject` to reduce repetition.\n\n[source,ruby]\n----\n# bad\nit { expect(hero.equipment).to be_heavy }\nit { expect(hero.equipment).to include 'sword' }\n\n# good\nsubject(:equipment) { hero.equipment }\n\nit { expect(equipment).to be_heavy }\nit { expect(equipment).to include 'sword' }\n----\n\n=== Named Subject [[use-subject]]\n\nUse named `subject` when possible.\nOnly use anonymous subject declaration when you don't reference it in any tests, e.g. when `is_expected` is used.\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n  subject { FactoryBot.create(:article) }\n\n  it 'is not published on creation' do\n    expect(subject).not_to be_published\n  end\nend\n\n# good\ndescribe Article do\n  subject { FactoryBot.create(:article) }\n\n  it 'is not published on creation' do\n    is_expected.not_to be_published\n  end\nend\n\n# even better\ndescribe Article do\n  subject(:article) { FactoryBot.create(:article) }\n\n  it 'is not published on creation' do\n    expect(article).not_to be_published\n  end\nend\n----\n\n=== Subject Naming in Context\n\nWhen 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.\n\n[source,ruby]\n----\n# bad\ndescribe Article do\n  context 'when there is an author' do\n    subject(:article) { FactoryBot.create(:article, author: user) }\n\n    it 'shows other articles by the same author' do\n      expect(article.related_stories).to include(story1, story2)\n    end\n  end\n\n  context 'when the author is anonymous' do\n    subject(:article) { FactoryBot.create(:article, author: nil) }\n\n    it 'matches stories by title' do\n      expect(article.related_stories).to include(story3, story4)\n    end\n  end\nend\n\n# good\ndescribe Article do\n  context 'when article has an author' do\n    subject(:article) { FactoryBot.create(:article, author: user) }\n\n    it 'shows other articles by the same author' do\n      expect(article.related_stories).to include(story1, story2)\n    end\n  end\n\n  context 'when the author is anonymous' do\n    subject(:guest_article) { FactoryBot.create(:article, author: nil) }\n\n    it 'matches stories by title' do\n      expect(guest_article.related_stories).to include(story3, story4)\n    end\n  end\nend\n----\n\n=== Don't Stub Subject\n\nDon't stub methods of the object under test, it's a code smell and often indicates a bad design of the object itself.\n\n[source,ruby]\n----\n# bad\ndescribe 'Article' do\n  subject(:article) { Article.new }\n\n  it 'indicates that the author is unknown' do\n    allow(article).to receive(:author).and_return(nil)\n    expect(article.description).to include('by an unknown author')\n  end\nend\n\n# good - with correct subject initialization\ndescribe 'Article' do\n  subject(:article) { Article.new(author: nil) }\n\n  it 'indicates that the author is unknown' do\n    expect(article.description).to include('by an unknown author')\n  end\nend\n\n# good - with better object design\ndescribe 'Article' do\n  subject(:presenter) { ArticlePresenter.new(article) }\n  let(:article) { Article.new }\n\n  it 'indicates that the author is unknown' do\n    allow(article).to receive(:author).and_return(nil)\n    expect(presenter.description).to include('by an unknown author')\n  end\nend\n----\n\n=== `it` and `specify`\n\nUse `specify` if the example doesn't have a description, use `it` for examples with descriptions.\nAn exception is one-line example, where `it` is preferable.\n`specify` is also useful when the docstring does not read well off of `it`.\n\n[source,ruby]\n----\n# bad\nit do\n  # ...\nend\n\nspecify 'it sends an email' do\n  # ...\nend\n\nspecify { is_expected.to be_truthy }\n\nit '#do_something is deprecated' do\n  ...\nend\n\n# good\nspecify do\n  # ...\nend\n\nit 'sends an email' do\n  # ...\nend\n\nit { is_expected.to be_truthy }\n\nspecify '#do_something is deprecated' do\n  ...\nend\n----\n\n=== `it` in Iterators\n\nDo not write iterators to generate tests.\nWhen 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.\n\n[source,ruby]\n----\n# bad\n[:new, :show, :index].each do |action|\n  it 'returns 200' do\n    get action\n    expect(response).to be_ok\n  end\nend\n\n# good - more verbose, but better for the future development\ndescribe 'GET new' do\n  it 'returns 200' do\n    get :new\n    expect(response).to be_ok\n  end\nend\n\ndescribe 'GET show' do\n  it 'returns 200' do\n    get :show\n    expect(response).to be_ok\n  end\nend\n\ndescribe 'GET index' do\n  it 'returns 200' do\n    get :index\n    expect(response).to be_ok\n  end\nend\n----\n\n=== Incidental State\n\nAvoid incidental state as much as possible.\n\n[source,ruby]\n----\n# bad\nit 'publishes the article' do\n  article.publish\n\n  # Creating another shared Article test object above would cause this\n  # test to break\n  expect(Article.count).to eq(2)\nend\n\n# good\nit 'publishes the article' do\n  expect { article.publish }.to change(Article, :count).by(1)\nend\n----\n\n=== DRY\n\nBe 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.\n\nIn 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.\nHowever, 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.\n\n=== Factories\n\nUse https://github.com/thoughtbot/factory_bot[Factory Bot] to create test data in integration tests.\nYou should very rarely have to use `ModelName.create` within an integration spec.\nDo *not* use fixtures as they are not nearly as maintainable as factories.\n\n[source,ruby]\n----\n# bad\nsubject(:article) do\n  Article.create(\n    title: 'Piccolina',\n    author: 'John Archer',\n    published_at: '17 August 2172',\n    approved: true\n  )\nend\n\n# good\nsubject(:article) { FactoryBot.create(:article) }\n----\n\nNOTE: When talking about unit tests the best practice would be to use neither fixtures nor factories.\nPut as much of your domain logic in libraries that can be tested without needing complex, time consuming setup with either factories or fixtures.\n\n=== Needed Data\n\nDo not load more data than needed to test your code.\n\n[source,ruby]\n----\n# good\nRSpec.describe User do\n  describe \".top\" do\n    subject { described_class.top(2) }\n\n    before { FactoryBot.create_list(:user, 3) }\n\n    it { is_expected.to have(2).items }\n  end\nend\n----\n\n=== Doubles\n\nPrefer using verifying doubles over normal doubles.\n\nVerifying 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.\n\nIn general, use doubles with more isolated/behavioral tests rather than with integration tests.\n\nNOTE: There is no justification for turning `verify_partial_doubles` configuration option off.\nThat will significantly reduce the confidence in partial doubles.\n\n[source,ruby]\n----\n# good - verifying instance double\narticle = instance_double('Article')\nallow(article).to receive(:author).and_return(nil)\n\npresenter = described_class.new(article)\nexpect(presenter.title).to include('by an unknown author')\n\n\n# good - verifying object double\narticle = object_double(Article.new, valid?: true)\nexpect(article.save).to be true\n\n\n# good - verifying partial double\nallow(Article).to receive(:find).with(5).and_return(article)\n\n\n# good - verifying class double\nnotifier = class_double('Notifier')\nexpect(notifier).to receive(:notify).with('suspended as')\n----\n\nNOTE: If you stub a method that could give a false-positive test result, you have gone too far.\n\n=== Dealing with Time\n\nAlways use https://github.com/travisjeffery/timecop[Timecop] instead of stubbing anything on Time or Date.\n\n[source,ruby]\n----\ndescribe InvoiceReminder do\n  subject(:time_with_offset) { described_class.new.get_offset_time }\n\n  # bad\n  it 'offsets the time 2 days into the future' do\n    current_time = Time.now\n    allow(Time).to receive(:now).and_return(current_time)\n    expect(time_with_offset).to eq(current_time + 2.days)\n  end\n\n  # good\n  it 'offsets the time 2 days into the future' do\n    Timecop.freeze(Time.now) do\n      expect(time_with_offset).to eq 2.days.from_now\n    end\n  end\nend\n----\n\n=== Stub HTTP Requests\n\nStub HTTP requests when the code is making them.\nAvoid hitting real external services.\n\nUse https://github.com/bblimke/webmock[webmock] and https://github.com/vcr/vcr[VCR] separately or https://marnen.github.io/webmock-presentation/webmock.html[together].\n\n[source,ruby]\n----\n# good\ncontext 'with unauthorized access' do\n  let(:uri) { 'http://api.lelylan.com/types' }\n\n  before { stub_request(:get, uri).to_return(status: 401, body: fixture('401.json')) }\n\n  it 'returns access denied' do\n    page.driver.get uri\n    expect(page).to have_content 'Access denied'\n  end\nend\n----\n\n[#declare-constants]\n=== Declare Constants\n\nDo not explicitly declare classes, modules, or constants in example groups.\nhttps://rspec.info/features/3-12/rspec-mocks/mutating-constants/[Stub constants instead].\n\nNOTE: Constants, including classes and modules, when declared in a block scope, are defined in global namespace, and leak between examples.\n\n[source,ruby]\n----\n# bad\ndescribe SomeClass do\n  CONSTANT_HERE = 'I leak into global namespace'\nend\n\n# good\ndescribe SomeClass do\n  before do\n    stub_const('CONSTANT_HERE', 'I only exist during this example')\n  end\nend\n\n# bad\ndescribe SomeClass do\n  class FooClass \u003c described_class\n    def double_that\n      some_base_method * 2\n    end\n  end\n\n  it { expect(FooClass.new.double_that).to eq(4) }\nend\n\n# good - anonymous class, no constant needs to be defined\ndescribe SomeClass do\n  let(:foo_class) do\n    Class.new(described_class) do\n      def double_that\n        some_base_method * 2\n      end\n    end\n  end\n\n  it { expect(foo_class.new.double_that).to eq(4) }\nend\n\n# good - constant is stubbed\ndescribe SomeClass do\n  before do\n    foo_class = Class.new(described_class) do\n                  def do_something\n                  end\n                end\n    stub_const('FooClass', foo_class)\n  end\n\n  it { expect(FooClass.new.double_that).to eq(4) }\nend\n----\n\n[#implicit-block-expectations]\n=== Implicit Block Expectations\n\nAvoid using implicit block expectations.\n\n[source,ruby]\n----\n# bad\nsubject { -\u003e { do_something } }\nit { is_expected.to change(something).to(new_value) }\n\n# good\nit 'changes something to a new value' do\n  expect { do_something }.to change(something).to(new_value)\nend\n----\n\n== Naming\n\n=== Context Descriptions\n\nContext 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.\n\nA typical description will be an adjunct phrase starting with 'when', 'with', 'without', or similar words.\n\n[source,ruby]\n----\n# bad - 'Summary user logged in no display name shows a placeholder'\ndescribe 'Summary' do\n context 'user logged in' do\n   context 'no display name' do\n     it 'shows a placeholder' do\n     end\n   end\n end\nend\n\n# good - 'Summary when the user is logged in when the display name is blank shows a placeholder'\ndescribe 'Summary' do\n context 'when the user is logged in' do\n   context 'when the display name is blank' do\n     it 'shows a placeholder' do\n     end\n   end\n end\nend\n----\n\n=== Example Descriptions\n\n`it`/`specify` block descriptions should never end with a conditional.\nThis is a code smell that the `it` most likely needs to be wrapped in a `context`.\n\n[source,ruby]\n----\n# bad\nit 'returns the display name if it is present' do\n  # ...\nend\n\n# good\ncontext 'when display name is present' do\n  it 'returns the display name' do\n    # ...\n  end\nend\n\n# This encourages the addition of negative test cases that might have\n# been overlooked\ncontext 'when display name is not present' do\n  it 'returns nil' do\n    # ...\n  end\nend\n----\n\n=== Keep Example Descriptions Short\n\nKeep example description shorter than 60 characters.\n\nWrite the example that documents itself, and generates proper\ndocumentation format output.\n\n[source,ruby]\n----\n# bad\nit 'rewrites \"should not return something\" as \"does not return something\"' do\n  # ...\nend\n\n# good\nit 'rewrites \"should not return something\"' do\n  expect(rewrite('should not return something')).to\n    eq 'does not return something'\nend\n\n# good - self-documenting\nspecify do\n  expect(rewrite('should not return something')).to\n    eq 'does not return something'\nend\n----\n\n=== \"Should\" in Example Docstrings[[should-in-it]]\n\nDo not write 'should' or 'should not' in the beginning of your example docstrings.\nThe descriptions represent actual functionality, not what might be happening.\nUse the third person in the present tense.\n\n[source,ruby]\n----\n# bad\nit 'should return the summary' do\n  # ...\nend\n\n# good\nit 'returns the summary' do\n  # ...\nend\n----\n\n=== Describe the Methods[[example-group-naming]]\n\nBe clear about what method you are describing.\nUse the Ruby documentation convention of `.` when referring to a class method's name and `#` when referring to an instance method's name.\n\n[source,ruby]\n----\n# bad\ndescribe 'the authenticate method for User' do\n  # ...\nend\n\ndescribe 'if the user is an admin' do\n  # ...\nend\n\n# good\ndescribe '.authenticate' do\n  # ...\nend\n\ndescribe '#admin?' do\n  # ...\nend\n----\n\n=== Use `expect`\n\nAlways use the newer `expect` syntax.\n\nConfigure RSpec to only accept the new `expect` syntax.\n\n[source,ruby]\n----\n# bad\nit 'creates a resource' do\n  response.should respond_with_content_type(:json)\nend\n\n# good\nit 'creates a resource' do\n  expect(response).to respond_with_content_type(:json)\nend\n----\n\n== Matchers\n\n=== Predicate Matchers\n\nUse RSpec's predicate matcher methods when possible.\n\n[source,ruby]\n----\ndescribe Article do\n  subject(:article) { FactoryBot.create(:article) }\n\n  # bad\n  it 'is published' do\n    expect(article.published?).to be true\n  end\n\n  # good\n  it 'is published' do\n    expect(article).to be_published\n  end\n\n  # even better\n  it { is_expected.to be_published }\nend\n----\n\n=== Built in Matchers\n\nUse built-in matchers.\n\n[source,ruby]\n----\n# bad\nit 'includes a title' do\n  expect(article.title.include?('a lengthy title')).to be true\nend\n\n# good\nit 'includes a title' do\n  expect(article.title).to include 'a lengthy title'\nend\n----\n\n=== `be` Matcher\n\nAvoid using `be` matcher without arguments.\nIt is too generic, as it pass on everything that is not `nil` or `false`.\nIf that is the exact intent, use `be_truthy`.\nIn all other cases it's better to specify what exactly is the expected value.\n\n[source,ruby]\n----\n# bad\nit 'has author' do\n  expect(article.author).to be\nend\n\n# good\nit 'has author' do\n  expect(article.author).to be_truthy # same as the original\n  expect(article.author).not_to be_nil # `be` is often used to check for non-nil value\n  expect(article.author).to be_an(Author) # explicit check for the type of the value\nend\n----\n\n=== Extract Common Expectation Parts into Matchers\n\nExtract frequently used common logic from your examples into https://rspec.info/features/3-12/rspec-expectations/custom-matchers/define-matcher/[custom matchers].\n\n[source,ruby]\n----\n# bad\nit 'returns JSON with temperature in Celsius' do\n  json = JSON.parse(response.body).with_indifferent_access\n  expect(json[:celsius]).to eq 30\nend\n\nit 'returns JSON with temperature in Fahrenheit' do\n  json = JSON.parse(response.body).with_indifferent_access\n  expect(json[:fahrenheit]).to eq 86\nend\n\n# good\nit 'returns JSON with temperature in Celsius' do\n  expect(response).to include_json(celsius: 30)\nend\n\nit 'returns JSON with temperature in Fahrenheit' do\n  expect(response).to include_json(fahrenheit: 86)\nend\n----\n\n=== `any_instance_of`\n\nAvoid using `allow_any_instance_of`/`expect_any_instance_of`.\nIt might be an indication that the object under test is too complex, and is ambiguous when used with receive counts.\n\n[source,ruby]\n----\n# bad\nit 'has a name' do\n  allow_any_instance_of(User).to receive(:name).and_return('Tweedledee')\n  expect(account.name).to eq 'Tweedledee'\nend\n\n# good\nlet(:account) { Account.new(user) }\n\nit 'has a name' do\n  allow(user).to receive(:name).and_return('Tweedledee')\n  expect(account.name).to eq 'Tweedledee'\nend\n----\n\n=== Matcher Libraries\n\nUse 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.\n\n[source,ruby]\n----\n# bad\ndescribe '#title' do\n  it 'is required' do\n    article.title = nil\n    article.valid?\n    expect(article.errors[:title])\n      .to contain_exactly('Article has no title')\n    not\n  end\nend\n\n# good\ndescribe '#title' do\n  it 'is required' do\n    expect(article).to validate_presence_of(:title)\n      .with_message('Article has no title')\n  end\nend\n----\n\n== Rails: Integration[[integration]][[rails]]\n\nTest what you see.\nDeeply test your models and your application behaviour (integration tests).\nDo not add useless complexity testing controllers.\n\nThis is an open debate in the Ruby community and both sides have good arguments supporting their idea.\nPeople supporting the need of testing controllers will tell you that your integration tests don't cover all use cases and that they are slow.\nBoth are wrong.\nIt is possible to cover all use cases and it's possible to make them fast.\n\n== Rails: Views[[views]]\n\n=== View Directory Structure\n\nThe directory structure of the view specs `spec/views` matches the one in `app/views`.\nFor example the specs for the views in `app/views/users` are placed in `spec/views/users`.\n\n=== View Spec File Name\n\nThe 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`.\n\n=== View Outer `describe`\n\nThe outer `describe` block uses the path to the view without the `app/views` part.\nThis is used by the `render` method when it is called without arguments.\n\n[source,ruby]\n----\n# spec/views/articles/new.html.erb_spec.rb\ndescribe 'articles/new.html.erb' do\n  # ...\nend\n----\n\n=== View Mock Models\n\nAlways mock the models in the view specs.\nThe purpose of the view is only to display information.\n\n=== View `assign`\n\nThe method `assign` supplies the instance variables which the view uses and are supplied by the controller.\n\n[source,ruby]\n----\n# spec/views/articles/edit.html.erb_spec.rb\ndescribe 'articles/edit.html.erb' do\n  it 'renders the form for a new article creation' do\n    assign(:article, double(Article).as_null_object)\n    render\n    expect(rendered).to have_selector('form',\n      method: 'post',\n      action: articles_path\n    ) do |form|\n      expect(form).to have_selector('input', type: 'submit')\n    end\n  end\nend\n----\n\n=== Capybara Negative Selectors[[view-capybara-negative-selectors]]\n\nPrefer capybara negative selectors over `to_not` with positive ones.\n\n[source,ruby]\n----\n# bad\nexpect(page).to_not have_selector('input', type: 'submit')\nexpect(page).to_not have_xpath('tr')\n\n# good\nexpect(page).to have_no_selector('input', type: 'submit')\nexpect(page).to have_no_xpath('tr')\n----\n\n=== View Helper Stub\n\nWhen a view uses helper methods, these methods need to be stubbed.\nStubbing the helper methods is done on the `template` object:\n\n[source,ruby]\n----\n# app/helpers/articles_helper.rb\nclass ArticlesHelper\n  def formatted_date(date)\n    # ...\n  end\nend\n----\n\n[source,ruby]\n----\n# app/views/articles/show.html.erb\n\u003c%= 'Published at: #{formatted_date(@article.published_at)}' %\u003e\n----\n\n[source,ruby]\n----\n# spec/views/articles/show.html.erb_spec.rb\ndescribe 'articles/show.html.erb' do\n  it 'displays the formatted date of article publishing' do\n    article = double(Article, published_at: Date.new(2012, 01, 01))\n    assign(:article, article)\n\n    allow(template).to_receive(:formatted_date).with(article.published_at).and_return('01.01.2012')\n\n    render\n    expect(rendered).to have_content('Published at: 01.01.2012')\n  end\nend\n----\n\n=== View Helpers\n\nThe helpers specs are separated from the view specs in the `spec/helpers` directory.\n\n== Rails: Controllers[[controllers]]\n\n=== Controller Models\n\nMock the models and stub their methods.\nTesting the controller should not depend on the model creation.\n\n=== Controller Behaviour\n\nTest only the behaviour the controller should be responsible about:\n\n* Execution of particular methods\n* Data returned from the action - assigns, etc.\n* Result from the action - template render, redirect, etc.\n\n[source,ruby]\n----\n# Example of a commonly used controller spec\n# spec/controllers/articles_controller_spec.rb\n# We are interested only in the actions the controller should perform\n# So we are mocking the model creation and stubbing its methods\n# And we concentrate only on the things the controller should do\n\ndescribe ArticlesController do\n  # The model will be used in the specs for all methods of the controller\n  let(:article) { double(Article) }\n\n  describe 'POST create' do\n    before { allow(Article).to receive(:new).and_return(article) }\n\n    it 'creates a new article with the given attributes' do\n      expect(Article).to receive(:new).with(title: 'The New Article Title').and_return(article)\n      post :create, message: { title: 'The New Article Title' }\n    end\n\n    it 'saves the article' do\n      expect(article).to receive(:save)\n      post :create\n    end\n\n    it 'redirects to the Articles index' do\n      allow(article).to receive(:save)\n      post :create\n      expect(response).to redirect_to(action: 'index')\n    end\n  end\nend\n----\n\n=== Controller Contexts\n\nUse context when the controller action has different behaviour depending on the received params.\n\n[source,ruby]\n----\n# A classic example for use of contexts in a controller spec is creation or update when the object saves successfully or not.\n\ndescribe ArticlesController do\n  let(:article) { double(Article) }\n\n  describe 'POST create' do\n    before { allow(Article).to receive(:new).and_return(article) }\n\n    it 'creates a new article with the given attributes' do\n      expect(Article).to receive(:new).with(title: 'The New Article Title').and_return(article)\n      post :create, article: { title: 'The New Article Title' }\n    end\n\n    it 'saves the article' do\n      expect(article).to receive(:save)\n      post :create\n    end\n\n    context 'when the article saves successfully' do\n      before do\n        allow(article).to receive(:save).and_return(true)\n      end\n\n      it 'sets a flash[:notice] message' do\n        post :create\n        expect(flash[:notice]).to eq('The article was saved successfully.')\n      end\n\n      it 'redirects to the Articles index' do\n        post :create\n        expect(response).to redirect_to(action: 'index')\n      end\n    end\n\n    context 'when the article fails to save' do\n      before do\n        allow(article).to receive(:save).and_return(false)\n      end\n\n      it 'assigns @article' do\n        post :create\n        expect(assigns[:article]).to eq(article)\n      end\n\n      it \"re-renders the 'new' template\" do\n        post :create\n        expect(response).to render_template('new')\n      end\n    end\n  end\nend\n----\n\n== Rails: Models[[models]]\n\n=== Model Mocks\n\nDo not mock the models in their own specs.\n\n=== Model Objects\n\nUse `FactoryBot.create` to make real objects, or just use a new (unsaved) instance with `subject`.\n\n[source,ruby]\n----\ndescribe Article do\n  subject(:article) { FactoryBot.create(:article) }\n\n  it { is_expected.to be_an Article }\n  it { is_expected.to be_persisted }\nend\n----\n\n=== Model Mock Associations\n\nIt is acceptable to mock other models or child objects.\n\n=== Avoid Duplication in Model Tests[[model-avoid-duplication]]\n\nCreate the model for all examples in the spec to avoid duplication.\n\n[source,ruby]\n----\ndescribe Article do\n  let(:article) { FactoryBot.create(:article) }\nend\n----\n\n=== Check Model Validity[[model-check-validity]]\n\nAdd an example ensuring that the model created with `FactoryBot.create` is valid.\n\n[source,ruby]\n----\ndescribe Article do\n  it 'is valid with valid attributes' do\n    expect(article).to be_valid\n  end\nend\n----\n\n=== Model Validations\n\nWhen testing validations, use `expect(model.errors[:attribute].size).to eq(x)` to specify the attribute which should be validated.\nUsing `be_valid` does not guarantee that the problem is in the intended attribute.\n\n[source,ruby]\n----\n# bad\ndescribe '#title' do\n  it 'is required' do\n    article.title = nil\n    expect(article).to_not be_valid\n  end\nend\n\n# preferred\ndescribe '#title' do\n  it 'is required' do\n    article.title = nil\n    article.valid?\n    expect(article.errors[:title].size).to eq(1)\n  end\nend\n----\n\n=== Separate Example Group for Attribute Validations[[model-separate-describe-for-attribute-validations]]\n\nAdd a separate `describe` for each attribute which has validations.\n\n[source,ruby]\n----\ndescribe '#title' do\n  it 'is required' do\n    article.title = nil\n    article.valid?\n    expect(article.errors[:title].size).to eq(1)\n  end\nend\n\ndescribe '#name' do\n  it 'is required' do\n    article.name = nil\n    article.valid?\n    expect(article.errors[:name].size).to eq(1)\n  end\nend\n----\n\n=== Naming Another Object[[model-name-another-object]]\n\nWhen testing uniqueness of a model attribute, name the other object `another_object`.\n\n[source,ruby]\n----\ndescribe Article do\n  describe '#title' do\n    it 'is unique' do\n      another_article = FactoryBot.create(:article, title: article.title)\n      article.valid?\n      expect(article.errors[:title].size).to eq(1)\n    end\n  end\nend\n----\n\n== Rails: Mailers[[mailers]]\n\n=== Mailer Mock Model\n\nThe model in the mailer spec should be mocked.\nThe mailer should not depend on the model creation.\n\n=== Mailer Expectations\n\nThe mailer spec should verify that:\n\n* the subject is correct\n* the sender e-mail is correct\n* the e-mail is sent to the correct recipient\n* the e-mail contains the required information\n\n[source,ruby]\n----\ndescribe SubscriberMailer do\n  let(:subscriber) { double(Subscription, email: 'johndoe@test.com', name: 'John Doe') }\n\n  describe 'successful registration email' do\n    subject(:email) { SubscriptionMailer.successful_registration_email(subscriber) }\n\n    it { is_expected.to have_attributes(subject: 'Successful Registration!', from: ['infor@your_site.com'], to: [subscriber.email]) }\n\n    it 'contains the subscriber name' do\n      expect(email.body.encoded).to match(subscriber.name)\n    end\n  end\nend\n----\n\n== Recommendations\n\n=== Correct Setup\n\nCorrectly 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`).\nUse `rspec --init` to generate `.rspec` and `spec/spec_helper.rb` files.\n\n----\n# .rspec\n--color\n--require spec_helper\n\n# .rspec-local\n--profile 2\n----\n\n== Related Guides\n\n* https://rubystyle.guide[Ruby Style Guide]\n* https://rails.rubystyle.guide[Rails Style Guide]\n* https://minitest.rubystyle.guide[Minitest Style Guide]\n\n== Contributing\n\nNothing written in this guide is set in stone.\nEveryone is welcome to contribute, so that we could ultimately create a resource that will be beneficial to the entire Ruby community.\n\nFeel free to open tickets or send pull requests with improvements.\nThanks in advance for your help!\n\nYou can also support the project (and RuboCop) with financial contributions via https://www.patreon.com/bbatsov[Patreon].\n\n=== How to Contribute?\n\nIt's easy, just follow the contribution guidelines below:\n\n* https://docs.github.com/en/get-started/quickstart/fork-a-repo[Fork] the https://github.com/rubocop/rspec-style-guide[project] on GitHub\n* Make your feature addition or bug fix in a feature branch\n* Include a http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[good description] of your changes\n* Push your feature branch to GitHub\n* 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]\n\n== License\n\nimage:https://i.creativecommons.org/l/by/3.0/88x31.png[Creative Commons License]\nThis work is licensed under a http://creativecommons.org/licenses/by/3.0/deed.en_US[Creative Commons Attribution 3.0 Unported License]\n\n== Credit\n\nInspiration was taken from the following:\n\nhttps://github.com/howaboutwe/rspec-style-guide[HowAboutWe's RSpec style guide]\n\nhttps://github.com/rubocop/rails-style-guide[Community Rails style guide]\n\nThis guide was maintained by https://github.com/reachlocal[ReachLocal] for a long while.\n\nThis 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubocop%2Frspec-style-guide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frubocop%2Frspec-style-guide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubocop%2Frspec-style-guide/lists"}