Keeping the source code of your application in good structure is important. But structuring the code of your test might be even more important: When I'm new to an app, my second look is at the tests, to both see what's the level of coverage and to look up details about the implementation.

Here are some tips how I structure my test code I’ve developed over time. Those should help you to keep your tests well structured, easy to read and comprehend, and DRY.

describe

Use describe both on the class level, as well as to describe methods. Use # to prefix instance methods and . for class methods:


# app/models/book.rb
class Book < ApplicationRecord
  def valid_isbn?
    # ...
  end

  def self.find_by_isbn(isbn)
    # ...
  end
end

# spec/models/book_rspec.rb
describe Book do
  describe '#valid_isbn?' do
    # ...
  end

  describe '.find_by_isbn' do
    # ...
  end
end

context

context starts either with "with" or "when", like "with a missing ISBN number” or "when API is down”:

# spec/models/book_rspec.rb
describe Book do
  describe '#valid_isbn?' do
    context 'with a valid ISBN number' do
      # ...
    end

    context 'with an invalid ISBN number' do
      # ...
    end
  end

  describe '#purchase' do
    context 'when the Amazon API is down' do
      # ...
    end
  end
end

it

it describes a test case. I dislike specify, because I often have the feeling that it doesn't convey a message. I also try to avoid the short form of it which omits the description. However, I use it for controller specs, when I'm testing for a response code or a response content type.

I do not use helper words like "should" or "will", so instead of "should return true" or "will return true", I write "returns true" - straight to the point.

# spec/models/book_rspec.rb
describe Book do
  describe '#valid_isbn?' do
    context 'with a valid ISBN' do
      it 'returns true' do
        # ...
      end
    end
  end

  describe '#purchase' do
    context 'when the Amazon API is down' do
      it 'throws a custom error' do
        # ...
      end
    end
  end
end

subject and subject!

Use subject. It makes it way clearer what you're actually testing and helps you stay DRY in your tests:

# spec/models/book_rspec.rb
describe Book do
  describe '#valid_isbn?' do
    subject { Book.new(isbn: isbn).valid_isbn? }

    context 'with a valid ISBN number' do
      let(:isbn) { 'valid' }

      # ...
    end

    context 'with an invalid ISBN number' do
      let(:isbn) { 'invalid' }

      # ...
    end
  end  
end

subject! is a real bonus, something that I haven’t discovered until a few weeks ago. I find it especially helpful in controller tests, where you would normally call an action in the before(:each) block:

# spec/controllers/books_controller_spec.rb
describe BooksController do
  describe '#show' do
    let(:book) { create(:book) }

    before(:each) do
      # You might do some stubbing & mocking here
      get :show, id: book.id
    end

    it 'returns a 200 OK' do
      expect(response.code).to eq 200
    end
  end
end

turns into

# spec/controllers/books_controller_spec.rb
describe BooksController do
  describe '#show' do
    let(:book) { create(:book) }

    subject! { get :show, id: book.id }

    it 'returns a 200 OK' do
      expect(response.code).to eq 200
    end
  end
end

before(:each) and before(:all)

Use before(:each) to setup method stubs and mocks.
Use before(:all) to setup configuration changes (disable/enable VCR, for example, or change a configuration object like Rails.configuration or similar).


A site I highly recommend is betterspecs.org, which is a great resource to go deeper and find additional tips. I'm curious to learn about your best practices - let me know in the comments or send me an email!