Skip to content

Instantly share code, notes, and snippets.

@jagdeepsingh
Last active June 14, 2019 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jagdeepsingh/02006a8c0d0c3a49c315 to your computer and use it in GitHub Desktop.
Save jagdeepsingh/02006a8c0d0c3a49c315 to your computer and use it in GitHub Desktop.
RSpec

RSpec

Contents:

1 Install gems

Add to Gemfile:

group :test do
  gem 'rspec-rails', '~> 3.7.2'
end

Run bundle install.

Back to Top

2 Request Specs

# spec/requests/users_spec.rb
require 'rails_helper'

RSpec.describe 'users', type: :request do
  describe 'GET /users' do
    it 'fetches list of users' do
      get '/users'
      # Or with params:
      #   get '/users', params: { active: true }
      
      expect(response).to have_http_status(:ok)
    end
  end
end

Back to Top

3 Support

You can define helper methods under spec/support/ to be used in various types of RSpecs e.g.:

# spec/support/request_helper.rb
module RequestHelper
  def json_response
    @json_response ||= JSON.parse(response.body, symbolize_names: true)
  end
end
# spec/rails_helper.rb
require 'support/request_helper'

RSpec.configure do |config|
  config.include RequestHelper, type: :request
end

Back to Top

4.1 Install gem

# Gemfile
gem 'mongoid-rspec', github: 'mongoid/mongoid-rspec', ref: '68c95b133be1a1482fe882e39afd33262147d1f4'

Latest released version of mongoid-rspec (4.0.0) does not support mongoid 7. Find out more here.

Run bundle install.

# spec/rails_helper.rb
require 'mongoid-rspec'

4.2 Usage

Note: This has been tested on version 1.13.0 of mongoid-rspec.

4.2.1 Model

describe Preference do
  # field
  it { is_expected.to have_field(:ln).of_type(String) }
  it { is_expected.to have_field(:ln).with_alias(:name).of_type(String) }
  it { is_expected.to have_field(:ln).with_alias(:name).of_type(String).localized }
  it { is_expected.to have_field(:ln).of_type(String).with_default_value_of('JD') }

  # embed_many
  it { is_expected.to embed_many(:bookings) }
  it { is_expected.to embed_many(:bookings).with_polymorphism }  
  it { is_expected.to embed_many(:bookings).of_type(Reservation) }
  it { is_expected.to embed_many(:bookings).stored_as(:bks) }
  it { is_expected.to embed_many(:bookings).with_cascading_callbacks }

  # embedded_in
  it { is_expected.to be_embedded_in(:user) }
  it { is_expected.to be_embedded_in(:preferable).with_polymorphism }
  it { is_expected.to be_embedded_in(:user).of_type(Person) }
end

Back to Top

5.1 Install gem

# Gemfile
gem 'webmock', '~> 3.4.2'

Run bundle install.

# spec/rails_helper.rb
require 'webmock/rspec'

5.2 Usage

# spec/services/users_api_spec.rb
RSpec.describe UsersApi do
  describe '#create' do
    let(:api_request_body) do
      { user: {
          first_name: 'Kevin',
          last_name: 'Omar' } }.to_json
    end
    let(:api_request_headers) do
      { 'Accept'       => 'application/json',
        'Content-Type' => 'application/json',
        'Host'         => 'localhost:3001',
        'User-Agent'   => 'Ruby'}
    end
    let(:api_response_body) do
      { customer_user: { id: 1, first_name: 'Kevin', last_name: 'Omar' } }.to_json
    end
    let!(:stub_api_request) do
      stub_request(:post, 'http://www.example.com/api/v1/users').
        with(body: api_request_body, headers: api_request_headers).
        to_return(body: api_response_body)
        
      # Or for error response, do:
      #   stub_request(...).to_return(status: 422)
    end
    before { do_action }
    it { expect(stub_api_request).to have_been_requested }
  end
end

Back to Top

Factory

FactoryGirl.define do
  factory :reservation do
    date { Date.today }
    people 4
  end
end

reservation = FactoryGirl.build(:reservation)
reservation.date      # Thu, 12 Jan 2017
reservation.people    # 4

Set unique values for an attribute.

FactoryGirl.define do
  factory :reservation do
    sequence(:title) { |n| "Foo-#{n}" }
    sequence(:code, 101) { |n| "#{n}" }
  end
end

reservation = FactoryGirl.build(:reservation)
reservation.title       # "Foo-0"
reservation.code        # "101"

other_reservation = FactoryGirl.build(:reservation)
other_reservation.title       # "Foo-1"
other_reservation.code        # "102"

Factory with a different name from the model.

FactoryGirl.define do
  factory :booking, class: Reservation do
  end
end

Define associations

FactoryGirl.define do
  factory :reservation do
    association :shop
    association :franchise, factory: :merchant
  end
end

Callbacks

FactoryGirl.define do
  factory :reservation do
    after :build do |reservation|
      reservation.customer = FactoryGirl.build(:customer)
    end

    after :create do |reservation|
      3.times { reservation.tables << FactoryGirl.create(:table) }
    end
  end
end

Traits

FactoryGirl.define do
  factory :reservation do
    trait :vip do
      is_vip true
    end

    trait :vvip do
      vip
      points 20
    end

    trait :with_tables do
      after :build do |reservation|
        3.times { reservation.tables << FactoryGirl.create(:table) }
      end
    end
  end
end

reservation = FactoryGirl.build(:reservation, :vip)
reservation = FactoryGirl.build(:reservation, :vvip, :with_tables)
reservation = FactoryGirl.build(:reservation, :vvip, :with_tables, code: "101")

You can also define a factory using existing traits.

FactoryGirl.define do
  factory :user do
    trait :senior do
      age 70
    end
    
    trait :special do
      is_special true
    end
    
    factory :senior_special_user, traits: %i(senior special)
  end
end

Models

require_relative 'spec_helper'
require_relative '../spec_helper'

describe Reservation do
  # let/let!
  let(:shop){ build(:shop) }
  let(:shop){ create(:shop) }
  let!(:shop){ create(:shop) }
  let(:reservation){ described_class.new }
  let(:reservation){ build(:reservation, shop: shop) }
  let(:reservation){ build(:reservation, :with_customer) }
  let(:reservation){ build(:reservation, :with_customer, shop: shop) }
  let(:reservation_attributes){ attributes_for(:reservation, shop: shop) }
  let(:customer){ reservation.customer }
  let(:customer) do
    c = reservation.customer
    c.name = 'JD'
    c.save!
    c
  end

  # methods
  def foo
  end

  shared_examples_for 'foo bar' do
    context 'when valid' do
      let(:shop){ build(:shop) }
      before { shop.save! }
      it { is_expected.to eq shop }
    end
  end

  it_behaves_like 'foo bar'

  # collection
  it { is_expected.to be_stored_in :reservations }

  # describe/context
  describe 'something' do
    context 'when case' do
      # Add test here
    end
  end

  # Conditional rspecs
  describe '#foo', if: reservation.respond_to?(:foo) do
  end

  # before/after
  before { reservation.shop = shop }

  # stub
  before { allow( shop ).to receive(:reservations).and_return([reservation]) }
  before { allow( shop ).to receive(:reservations).with(:online).and_return([reservation]) }

  # subject
  subject { shop }
  subject { shop.reservations }

  # respond_to
  it { is_expected.to respond_to :shop }
  it { is_expected.to respond_to :shop= }

  # slugify
  it { is_expected.to have_slug_for(:name) }
  it { is_expected.to have_slug_for(:name).with_scope(:shop_id) }

  # associations
  it { is_expected.to belong_to(:shop) }
  it { is_expected.to belong_to(:shop).of_type(Shop) }
  it { is_expected.to belong_to(:shop).as_inverse_of(nil) }

  it { is_expected.to have_many(:customers) }
  it { is_expected.to have_many(:customers).of_type(Customer) }

  it { is_expected.to have_and_belong_to_many(:customers) }
  it { is_expected.to have_and_belong_to_many(:customers).of_type(Customer) }

  # mass-assignment
  it { is_expected.to allow_mass_assignment_of :start_date }

  # index
  it { is_expected.to have_index_for(shop_id: 1) }
  it { is_expected.to have_index_for(shop_id: 1, menu_category_id: 1) }
  it { is_expected.to have_index_for('partner_refs.partner_id' => 1, deleted_at: 1) }

  # callbacks
  it { is_expected.to callback(:normalize_date_ranges!).before(:validation) }
  it { is_expected.to callback(:normalize_date_ranges!).after(:validation) }

  # validations
  it { is_expected.to validate_presence_of :shop_id }
  it { is_expected.to validate_uniqueness_of(:name) }
  it { is_expected.to validate_uniqueness_of(:name).scoped_to(:shop_id) }
  it { is_expected.to validate_uniqueness_of(:name).allow_blank?(true) }

  it { is_expected.to validate_length_of(:currency).is(3) }
  it { is_expected.to validate_length_of(:currency).within(3..5) }

  it { is_expected.to validate_numericality_of(:service_fee_pct) }
  it { is_expected.to validate_numericality_of(:service_fee_pct).to_allow(only_integer: true) }
  it { is_expected.to validate_numericality_of(:service_fee_pct).greater_than_or_equal_to(0) }
  it { is_expected.to validate_numericality_of(:service_fee_pct).less_than_or_equal_to(1) }
  it { is_expected.to validate_inclusion_of(:min_advance_time).to_allow(0900) }
  it { is_expected.to validate_format_of(:email).with_format(Devise.email_regexp) }
  it { is_expected.to validate_format_of(:email).to_allow('abc@test.com') }
  it { is_expected.to validate_format_of(:email).not_to_allow('invalid-email') }
  it { is_expected.to allow_value('abc@b.c').for :email }
  it { is_expected.to_not allow_value('a@b.c').for :email }

  # methods
  it { expect( Reservation ).to receive(:foo).with(arg1, arg2) }
  it { described_class.any_instance.should receive(:foo) }
  it { described_class.any_instance.should receive(:foo).with(:arg1, :arg2) }
  it { described_class.any_instance.should receive(:foo).with(:arg1, :arg2).and_return(nil) }
  it { expect{ subject.foo }.to raise_error }

  # matchers to/to_not
  it { is_expected.to be_a String }
  it { is_expected.to be_nil }
  it { is_expected.to_not be_nil }
  it { is_expected.to be_empty }
  it { is_expected.to be_truthy }
  it { is_expected.to be_falsey }
  it { is_expected.to be_valid }
  it { is_expected.to be true }
  it { is_expected.to be false }
  it { is_expected.to eq shop }
  it { is_expected.to include shop }
end

modules

# app/models/user.rb
class User
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::Paranoia
end
# specs/models/user_spec.rb
describe User do
  it { is_expected.to be_timestamped_document }
  it { is_expected.to be_paranoid_document }
end

with Mongoid::Timestamps::Short, do

it { is_expected.to be_timestamped_document.with(:short) }

embed_one

# app/models/user.rb
class User
  include Mongoid::Document
  
  embeds_one :address_a
  embeds_one :address_b, as: :addressable
  embeds_one :address_c, class_name: 'PostalAddress'
  embeds_one :address_d, store_as: :adrs
  embeds_one :address_e, cascade_callbacks: true
end
# specs/models/user_spec.rb
describe User do
  it { is_expected.to embed_one(:address_a) }
  it { is_expected.to embed_one(:address_b).with_polymorphism }
  it { is_expected.to embed_one(:address_c).of_type(PostalAddress) }
  it { is_expected.to embed_one(:address_d).stored_as(:adrs) }
  it { is_expected.to embed_one(:address_e).with_cascading_callbacks }
end

accept_nested_attributes_for

# specs/models/user_spec.rb
describe User do
  it { is_expected.to accept_nested_attributes_for :bookings }
end

has_mongoid_attached_file

# app/models/user.rb
class User
  include Mongoid::Document
  include Mongoid::Paperclip
  
  has_mongoid_attached_file :avatar
end
# specs/models/user_spec.rb
describe User do
  it { is_expected.to respond_to :avatar }
  it { is_expected.to respond_to :avatar= }
  it { is_expected.to have_field(:avatar_file_name).of_type(String) }
  it { is_expected.to have_field(:avatar_content_type).of_type(String) }
  it { is_expected.to have_field(:avatar_file_size).of_type(Integer) }
  it { is_expected.to have_field(:avatar_updated_at).of_type(DateTime) }
  it { is_expected.to have_field(:avatar_fingerprint).of_type(String) }
end

More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment