Rails Testing Best Practices
--- layout: default ---

describe.title

describe.body

shared.bad_code

describe 'the authenticate method for User' do
describe 'if the user is an admin' do

shared.good_code

describe '.authenticate' do
describe '#admin?' do

shared.discussion

contexts.title

contexts.body

shared.bad_code

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

shared.good_code

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

shared.discussion

short.title

short.body

shared.bad_code

it 'has 422 status code if an unexpected params will be added' do

shared.good_code

context 'when not valid' do
  it { is_expected.to respond_with 422 }
end

short.example.description

short.example.title

when not valid
  it should respond with 422

shared.discussion

single.title

single.body.first

shared.good_isolated_code

it { is_expected.to respond_with_content_type(:json) }
it { is_expected.to assign_to(:resource) }

single.body.second

shared.good_not_isolated_code

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

shared.discussion

all.title

all.body.first

all.example_title

before_action :find_owned_resources
before_action :find_resource

def destroy
  render 'show'
  @consumption.destroy
end

all.body.second

shared.bad_code

it 'shows the resource'

shared.good_code

describe '#destroy' do

  context 'when resource is found' do
    it 'responds with 200'
    it 'shows the resource'
  end

  context 'when resource is not found' do
    it 'responds with 404'
  end

  context 'when resource is not owned' do
    it 'responds with 404'
  end
end

shared.discussion

expect.title

expect.body.first

shared.bad_code

it 'creates a resource' do
  response.should respond_with_content_type(:json)
end

shared.good_code

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

expect.body.second

shared.good_code

# spec_helper.rb
RSpec.configure do |config|
  # ...
  config.expect_with :rspec do |c|
    c.syntax = :expect
  end
end

expect.body.third

shared.bad_code

context 'when not valid' do
  it { should respond_with 422 }
end

shared.good_code

context 'when not valid' do
  it { is_expected.to respond_with 422 }
end

expect.body.fourth

shared.discussion

subject.title

subject.body.first

shared.bad_code

it { expect(assigns('message')).to match /it was born in Belville/ }

shared.good_code

subject { assigns('message') }
it { is_expected.to match /it was born in Billville/ }

subject.body.second

shared.good_code

subject(:hero) { Hero.first }
it "carries a sword" do
  expect(hero.equipment).to include "sword"
end

shared.discussion

let.title

let.body.first

shared.bad_code

describe '#type_id' do
  before { @resource = FactoryBot.create :device }
  before { @type     = Type.find @resource.type_id }

  it 'sets the type_id field' do
    expect(@resource.type_id).to eq(@type.id)
  end
end

shared.good_code

describe '#type_id' do
  let(:resource) { FactoryBot.create :device }
  let(:type)     { Type.find resource.type_id }

  it 'sets the type_id field' do
    expect(resource.type_id).to eq(type.id)
  end
end

let.body.second

shared.good_code

context 'when updates a not existing property value' do
  let(:properties) { { id: Settings.resource_id, value: 'on'} }

  def update
    resource.properties = properties
  end

  it 'raises a not found error' do
    expect { update }.to raise_error Mongoid::Errors::DocumentNotFound
  end
end

let.body.third

Explanation

# this use of let
let(:foo) { Foo.new }

# is very nearly equivalent to this:
def foo
  @foo ||= Foo.new
end

shared.discussion

mock.title

mock.body.first

shared.good_code

# simulate a not found resource
context "when not found" do

  before do
    allow(Resource).to receive(:where).with(created_from: params[:id])
      .and_return(false)
  end

  it { is_expected.to respond_with 404 }
end

mock.body.second

shared.discussion

data.title

data.body

shared.good_code

describe "User" do
  describe ".top" do
    before { FactoryBot.create_list(:user, 3) }
    it { expect(User.top(2)).to have(2).item }
  end
end

shared.discussion

factories.title

factories.body.first

shared.bad_code

user = User.create(
  name: 'Genoveffa',
  surname: 'Piccolina',
  city: 'Billyville',
  birth: '17 Agoust 1982',
  active: true
)

shared.good_code

user = FactoryBot.create :user

factories.body.second

shared.discussion

matchers.title

matchers.body

shared.bad_code

lambda { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound

shared.good_code

expect { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound

shared.discussion

shared_examples.title

shared_examples.body.first

shared.bad_code

describe 'GET /devices' do

  let!(:resource) { FactoryBot.create :device, created_from: user.id }
  let!(:uri)      { '/devices' }

  context 'when shows all resources' do

    let!(:not_owned) { FactoryBot.create factory }

    it 'shows all owned resources' do
      page.driver.get uri
      expect(page.status_code).to be(200)
      contains_owned_resource resource
      does_not_contain_resource not_owned
    end
  end

  describe '?start=:uri' do

    it 'shows the next page' do

      page.driver.get uri, start: resource.uri
      expect(page.status_code).to be(200)
      contains_resource resources.first
      expect(page).to_not have_content resource.id.to_s
    end
  end
end

shared.good_code

describe 'GET /devices' do

  let!(:resource) { FactoryBot.create :device, created_from: user.id }
  let!(:uri)       { '/devices' }

  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

shared_examples.body.second

shared.discussion

should.title

should.body.first

shared.bad_code

it 'should not change timings' do
  consumption.occur_at.should == valid.occur_at
end

shared.good_code

it 'does not change timings' do
  expect(consumption.occur_at).to eq(valid.occur_at)
end

should.body.second

shared.discussion

continuous.title

continuous.body.first

shared.good_code

bundle exec guard

continuous.body.second

shared.good_code

guard 'rspec', cli: '--drb --format Fuubar --color', version: 2 do
  # run every updated spec file
  watch(%r{^spec/.+_spec\.rb$})
  # run the lib specs when a file in lib/ changes
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
  # run the model specs related to the changed model
  watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
  # run the view specs related to the changed view
  watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
  # run the integration specs related to the changed controller
  watch(%r{^app/controllers/(.+)\.rb}) { |m| "spec/requests/#{m[1]}_spec.rb" }
  # run all integration tests when application controller change
  watch('app/controllers/application_controller.rb') { "spec/requests" }
end

continuous.body.third

shared.discussion

stubbing.title

stubbing.body.first

shared.good_code

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 "gets a not authorized notification" do
    page.driver.get uri
    expect(page).to have_content 'Access denied'
  end
end

stubbing.body.second

shared.discussion

formatter.title

formatter.body.first

shared.good_code

# Gemfile
group :development, :test do
  gem 'fuubar'

# .rspec configuration file
--drb
--format Fuubar
--color

formatter.body.second

shared.discussion

contributing.title

contributing.body.first

contributing.body.second

contributing.body.third