Shoulda Matchers

repository·main·Indexed 23 days ago

https://github.com/thoughtbot/shoulda-matchers

A library providing RSpec and Minitest-compatible one-liners to test common Rails functionality. It reduces the complexity of manual testing for ActiveRecord, ActiveModel, ActionController, and Routing. Supported versions include Ruby 3.3+, Rails 7.2+, RSpec 3.x, and Minitest 5.x, with compatibility versions available for older environments.

Tokens
8K
Snippets
29
Records
36
Agent score
83%

What's inside shoulda-matchers

  1. Override the subject for testing validations

    main

    The subject is an implicit reference to the object under test. For validations, it is often better to provide a valid instance (e.g., using FactoryBot) rather than a fresh, invalid one.

    Note: When overriding the subject, always provide an instance of the class under test. In controller tests, avoid using subject { get :index } if you need to use matchers like permit; instead, use before { get :index } and assert against the response object.

    # RSpec with custom subject
    RSpec.describe Post, type: :model do
      describe 'validations' do
        subject { build(:post) }
    
        it { should validate_presence_of(:title) }
      end
    end
    
    # RSpec Controller pattern to avoid subject issues
    RSpec.describe PostsController, type: :controller do
      describe 'GET #index' do
        before { get :index }
    
        it { expect(response).to have_http_status(:success) }
        it { should render_template('index') }
      end
    end
  2. Configure shoulda-matchers for Minitest in Rails apps

    main

    To use shoulda-matchers with Minitest in a Rails application, add the following configuration to the bottom of test/test_helper.rb.

    Shoulda::Matchers.configure do |config|
      config.integrate do |with|
        with.test_framework :minitest
        with.library :rails
      end
    end
  3. Use Shoulda Matchers in RSpec and Minitest

    main

    Shoulda Matchers provides a one-liner syntax for testing Rails components using the should macro. It supports ActiveRecord models, ActiveModel models, ActionController, and Routing (RSpec only).

    RSpec Example

    RSpec.describe MenuItem, type: :model do
      describe 'associations' do
        it { should belong_to(:category).class_name('MenuCategory') }
      end
    
      describe 'validations' do
        it { should validate_presence_of(:name) }
        it { should validate_uniqueness_of(:name).scoped_to(:category_id) }
      end
    end

    Minitest (Shoulda) Example

    class MenuItemTest < ActiveSupport::TestCase
      context 'associations' do
        should belong_to(:category).class_name('MenuCategory')
      end
    
      context 'validations' do
        should validate_presence_of(:name)
        should validate_uniqueness_of(:name).scoped_to(:category_id)
      end
    end
    # RSpec
    RSpec.describe MenuItem, type: :model do
      describe 'associations' do
        it { should belong_to(:category).class_name('MenuCategory') }
      end
    
      describe 'validations' do
        it { should validate_presence_of(:name) }
        it { should validate_uniqueness_of(:name).scoped_to(:category_id) }
      end
    end
    
    # Minitest (Shoulda)
    class MenuItemTest < ActiveSupport::TestCase
      context 'associations' do
        should belong_to(:category).class_name('MenuCategory')
      end
    
      context 'validations' do
        should validate_presence_of(:name)
        should validate_uniqueness_of(:name).scoped_to(:category_id)
      end
    end
  4. Configure shoulda-matchers for RSpec in non-Rails apps

    main

    If you are using RSpec with ActiveRecord or ActiveModel in a non-Rails project, add this configuration to the bottom of spec/spec_helper.rb to enable the appropriate libraries.

    Shoulda::Matchers.configure do |config|
      config.integrate do |with|
        with.test_framework :rspec
    
        # Keep as many of these lines as are necessary:
        with.library :active_record
        with.library :active_model
      end
    end
  5. Configure shoulda-matchers for Minitest in non-Rails apps

    main

    If you are using Minitest with ActiveRecord or ActiveModel in a non-Rails project, add this configuration to the bottom of test/test_helper.rb.

    Shoulda::Matchers.configure do |config|
      config.integrate do |with|
        with.test_framework :minitest
    
        # Keep as many of these lines as are necessary:
        with.library :active_record
        with.library :active_model
      end
    end
  6. Install shoulda-matchers for Minitest

    main

    If you are using the shoulda umbrella gem, ensure it is updated to the latest version. Otherwise, add shoulda-matchers directly to your Gemfile in the :test group and run bundle install.

    group :test do
      gem 'shoulda-matchers', '~> 8.0'
    end
  7. Configure matcher availability in RSpec (Non-Rails projects)

    main

    In Rails projects, matchers are automatically available in specific example groups based on tags (e.g., type: :model). In non-Rails projects, you must manually include the matcher modules.

    To include them in a specific group:

    RSpec.describe MySpecialModel do
      include Shoulda::Matchers::ActiveModel
      include Shoulda::Matchers::ActiveRecord
    end

    To configure them globally for all groups with a specific tag (e.g., in rails_helper.rb):

    RSpec.configure do |config|
      config.include(Shoulda::Matchers::ActiveModel, type: :model)
      config.include(Shoulda::Matchers::ActiveRecord, type: :model)
    end
  8. Configure shoulda-matchers for RSpec in Rails apps

    main

    To use shoulda-matchers with RSpec in a Rails application, add the following configuration to the bottom of spec/rails_helper.rb (or a support file). This tells the gem to integrate with both the RSpec test framework and the Rails library.

    Shoulda::Matchers.configure do |config|
      config.integrate do |with|
        with.test_framework :rspec
        with.library :rails
      end
    end
  9. Configure shoulda-matchers integration

    main

    To use shoulda-matchers, you must configure it to integrate with your chosen test framework (e.g., :rspec) and libraries (e.g., :rails). This is done using the Shoulda::Matchers.configure block and the integrate method. If no framework or library is specified, a ConfigurationError will be raised.

    Shoulda::Matchers.configure do |config|
      config.integrate do |with|
        with.test_framework :rspec
        with.library :rails
      end
    end
  10. Test ActiveModel confirmation validations with `validate_confirmation_of`

    main

    The validate_confirmation_of matcher tests the usage of the validates_confirmation_of validation in ActiveModel. It can be used with both RSpec and Minitest (Shoulda).

    Basic Usage

    RSpec

    RSpec.describe User, type: :model do
      it { should validate_confirmation_of(:email) }
    end

    Minitest

    class UserTest < ActiveSupport::TestCase
      should validate_confirmation_of(:email)
    end

    Qualifiers

    .on(context)

    Use .on if your validation applies only under a specific context (e.g., :create).

    RSpec

    it { should validate_confirmation_of(:password).on(:create) }

    Minitest

    should validate_confirmation_of(:password).on(:create)

    .with_message(message)

    Use .with_message if you are using a custom validation message.

    RSpec

    it do
      should validate_confirmation_of(:password).with_message('Please re-enter your password')
    end

    Minitest

    should validate_confirmation_of(:password).with_message('Please re-enter your password')

    Multiple Attributes

    You can pass multiple attributes to the matcher to assert that each one has the validation. Any qualifier chained to the matcher is applied to every attribute uniformly.

    RSpec

    it { should validate_confirmation_of(:password, :email) }

    Minitest

    should validate_confirmation_of(:password, :email)
    class User
      include ActiveModel::Model
      attr_accessor :email
    
      validates_confirmation_of :email
    end
    
    # RSpec
    RSpec.describe User, type: :model do
      it { should validate_confirmation_of(:email) }
    end
    
    # Minitest (Shoulda)
    class UserTest < ActiveSupport::TestCase
      should validate_confirmation_of(:email)
    end