[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7832":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":23,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":29,"readmeContent":30,"aiSummary":31,"trendingCount":16,"starSnapshotCount":16,"syncStatus":32,"lastSyncTime":33,"discoverSource":34},7832,"shoulda-matchers","thoughtbot\u002Fshoulda-matchers","thoughtbot","Simple one-liner tests for common Rails functionality","https:\u002F\u002Fmatchers.shoulda.io",null,"Ruby",3583,915,75,37,0,1,12,30.89,"MIT License",false,"main",true,[25,26,27,28],"rails","rspec","ruby","testing","2026-06-12 02:01:45","# Shoulda Matchers [![Gem Version][version-badge]][rubygems] [![Build Status][github-actions-badge]][github-actions] [![Total Downloads][downloads-total]][rubygems] [![Downloads][downloads-badge]][rubygems]\n\n[version-badge]: https:\u002F\u002Fimg.shields.io\u002Fgem\u002Fv\u002Fshoulda-matchers.svg\n[rubygems]: https:\u002F\u002Frubygems.org\u002Fgems\u002Fshoulda-matchers\n[github-actions-badge]: https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Fthoughtbot\u002Fshoulda-matchers\u002Fci.yml?branch=main\n[github-actions]: https:\u002F\u002Fgithub.com\u002Fthoughtbot\u002Fshoulda-matchers\u002Factions\n[downloads-total]: https:\u002F\u002Fimg.shields.io\u002Fgem\u002Fdt\u002Fshoulda-matchers.svg\n[downloads-badge]: https:\u002F\u002Fimg.shields.io\u002Fgem\u002Fdtv\u002Fshoulda-matchers.svg\n[downloads-badge]: https:\u002F\u002Fimg.shields.io\u002Fgem\u002Fdtv\u002Fshoulda-matchers.svg\n\n[![shoulda-matchers][logo]][website]\n\n[logo]: https:\u002F\u002Fmatchers.shoulda.io\u002Fimages\u002Fshoulda-matchers-logo.png\n[website]: https:\u002F\u002Fmatchers.shoulda.io\u002F\n\nShoulda Matchers provides RSpec and Minitest-compatible one-liners to test\ncommon Rails functionality that, if written by hand, would be much longer, more\ncomplex, and error-prone.\n\n## Quick links\n\n📖 **[Read the documentation for the latest version][rubydocs].**\n📢 **[See what's changed in recent versions][changelog].**\n\n[rubydocs]: https:\u002F\u002Fmatchers.shoulda.io\u002Fdocs\n[changelog]: CHANGELOG.md\n\n## Table of contents\n\n- [Getting started](#getting-started)\n   - [RSpec](#rspec)\n    - [Rails apps](#rails-apps)\n    - [Non-Rails apps](#non-rails-apps)\n  - [Minitest](#minitest)\n    - [Rails apps](#rails-apps-1)\n    - [Non-Rails apps](#non-rails-apps-1)\n- [Usage](#usage)\n  - [On the subject of `subject`](#on-the-subject-of-subject)\n  - [Availability of RSpec matchers in example groups](#availability-of-rspec-matchers-in-example-groups)\n    - [Rails projects](#rails-projects)\n    - [Non-Rails projects](#non-rails-projects)\n  - [`should` vs `is_expected.to`](#should-vs-is_expectedto)\n  - [A Note on Testing Style](#a-note-on-testing-style)\n- [Matchers](#matchers)\n  - [ActiveModel matchers](#activemodel-matchers)\n  - [ActiveRecord matchers](#activerecord-matchers)\n  - [ActionController matchers](#actioncontroller-matchers)\n  - [Routing matchers](#routing-matchers)\n  - [Independent matchers](#independent-matchers)\n- [Extensions](#extensions)\n- [Contributing](#contributing)\n- [Compatibility](#compatibility)\n- [Versioning](#versioning)\n- [Team](#team)\n- [Copyright\u002FLicense](#copyrightlicense)\n- [About thoughtbot](#about-thoughtbot)\n\n## Getting started\n\n### RSpec\n\nStart by including `shoulda-matchers` in your Gemfile:\n\n```ruby\ngroup :test do\n  gem 'shoulda-matchers', '~> 7.0'\nend\n```\n\nThen run `bundle install`.\n\nNow you need to configure the gem by telling it:\n\n- which matchers you want to use in your tests\n- that you're using RSpec so that it can make those matchers available in\n  your example groups\n\n#### Rails apps\n\nIf you're working on a Rails app, simply place this at the bottom of\n`spec\u002Frails_helper.rb` (or in a support file if you so choose):\n\n```ruby\nShoulda::Matchers.configure do |config|\n  config.integrate do |with|\n    with.test_framework :rspec\n    with.library :rails\n  end\nend\n```\n\n#### Non-Rails apps\n\nIf you're not working on a Rails app, but you still make use of ActiveRecord or\nActiveModel in your project, you can still use this gem too! In that case,\nyou'll want to place the following configuration at the bottom of\n`spec\u002Fspec_helper.rb`:\n\n```ruby\nShoulda::Matchers.configure do |config|\n  config.integrate do |with|\n    with.test_framework :rspec\n\n    # Keep as many of these lines as are necessary:\n    with.library :active_record\n    with.library :active_model\n  end\nend\n```\n\n### Minitest\n\nIf you're using our umbrella gem [Shoulda], then make sure that you're using the\nlatest version:\n\n```ruby\ngroup :test do\n  gem 'shoulda', '~> 4.0'\nend\n```\n\n[Shoulda]: https:\u002F\u002Fgithub.com\u002Fthoughtbot\u002Fshoulda\n\nOtherwise, add `shoulda-matchers` to your Gemfile:\n\n```ruby\ngroup :test do\n  gem 'shoulda-matchers', '~> 7.0'\nend\n```\n\nThen run `bundle install`.\n\nNow you need to configure the gem by telling it:\n\n- which matchers you want to use in your tests\n- that you're using Minitest so that it can make those matchers available in\n  your test case classes\n\n#### Rails apps\n\nIf you're working on a Rails app, simply place this at the bottom of\n`test\u002Ftest_helper.rb`:\n\n```ruby\nShoulda::Matchers.configure do |config|\n  config.integrate do |with|\n    with.test_framework :minitest\n    with.library :rails\n  end\nend\n```\n\n#### Non-Rails apps\n\nIf you're not working on a Rails app, but you still make use of ActiveRecord or\nActiveModel in your project, you can still use this gem too! In that case,\nyou'll want to place the following configuration at the bottom of\n`test\u002Ftest_helper.rb`:\n\n```ruby\nShoulda::Matchers.configure do |config|\n  config.integrate do |with|\n    with.test_framework :minitest\n\n    # Keep as many of these lines as are necessary:\n    with.library :active_record\n    with.library :active_model\n  end\nend\n```\n\n## Usage\n\nMost of the matchers provided by this gem are useful in a Rails context, and as\nsuch, can be used for different parts of a Rails app:\n\n- [database models backed by ActiveRecord](#activerecord-matchers)\n- [non-database models, form objects, etc. backed by\n  ActiveModel](#activemodel-matchers)\n- [controllers](#actioncontroller-matchers)\n- [routes](#routing-matchers) (RSpec only)\n- [Rails-specific features like `delegate`](#independent-matchers)\n\nAs the name of the gem indicates, most matchers are designed to be used in\n\"one-liner\" form using the `should` macro, a special directive available in both\nRSpec and [Shoulda]. For instance, a model test case may look something like:\n\n```ruby\n# RSpec\nRSpec.describe MenuItem, type: :model do\n  describe 'associations' do\n    it { should belong_to(:category).class_name('MenuCategory') }\n  end\n\n  describe 'validations' do\n    it { should validate_presence_of(:name) }\n    it { should validate_uniqueness_of(:name).scoped_to(:category_id) }\n  end\nend\n\n# Minitest (Shoulda)\nclass MenuItemTest \u003C ActiveSupport::TestCase\n  context 'associations' do\n    should belong_to(:category).class_name('MenuCategory')\n  end\n\n  context 'validations' do\n    should validate_presence_of(:name)\n    should validate_uniqueness_of(:name).scoped_to(:category_id)\n  end\nend\n```\n\n[See below](#matchers) for the full set of matchers that you can use.\n\n### On the subject of `subject`\n\nFor both RSpec and Shoulda, the **subject** is an implicit reference to the\nobject under test, and through the use of `should` as demonstrated above, all of\nthe matchers make use of `subject` internally when they are run. A `subject` is\nalways set automatically by your test framework in any given test case; however,\nin certain cases it can be advantageous to override it. For instance, when\ntesting validations in a model, it is customary to provide a valid model instead\nof a fresh one:\n\n```ruby\n# RSpec\nRSpec.describe Post, type: :model do\n  describe 'validations' do\n    # Here we're using FactoryBot, but you could use anything\n    subject { build(:post) }\n\n    it { should validate_presence_of(:title) }\n  end\nend\n\n# Minitest (Shoulda)\nclass PostTest \u003C ActiveSupport::TestCase\n  context 'validations' do\n    subject { build(:post) }\n\n    should validate_presence_of(:title)\n  end\nend\n```\n\nWhen overriding the subject in this manner, then, it's important to provide the\ncorrect object. **When in doubt, provide an instance of the class under test.**\nThis is particularly necessary for controller tests, where it is easy to\naccidentally write something like:\n\n```ruby\nRSpec.describe PostsController, type: :controller do\n  describe 'GET #index' do\n    subject { get :index }\n\n    # This may work...\n    it { should have_http_status(:success) }\n    # ...but this will not!\n    it { should permit(:title, :body).for(:post) }\n  end\nend\n```\n\nIn this case, you would want to use `before` rather than `subject`:\n\n```ruby\nRSpec.describe PostsController, type: :controller do\n  describe 'GET #index' do\n    before { get :index }\n\n    # Notice that we have to assert have_http_status on the response here...\n    it { expect(response).to have_http_status(:success) }\n    # ...but we do not have to provide a subject for render_template\n    it { should render_template('index') }\n  end\nend\n```\n\n### Availability of RSpec matchers in example groups\n\n#### Rails projects\n\nIf you're using RSpec, then you're probably familiar with the concept of example\ngroups. Example groups can be assigned tags to assign different behaviors\nto different kinds of example groups. This comes into play especially when using\n`rspec-rails`, where, for instance, controller example groups, tagged with\n`type: :controller`, are written differently than request example groups, tagged\nwith `type: :request`. This difference in writing style arises because\n`rspec-rails` mixes different behaviors and methods into controller example\ngroups vs. request example groups.\n\nRelying on this behavior, Shoulda Matchers automatically makes certain matchers\navailable in certain kinds of example groups:\n\n- ActiveRecord and ActiveModel matchers are available only in model example\n  groups, i.e., those tagged with `type: :model` or in files located under\n  `spec\u002Fmodels`.\n- ActionController matchers are available only in controller example groups,\n  i.e., those tagged with `type: :controller` or in files located under\n  `spec\u002Fcontrollers`.\n- The `route` matcher is available in routing example groups, i.e., those\n  tagged with `type: :routing` or in files located under `spec\u002Frouting`.\n- Independent matchers are available in all example groups.\n\nAs long as you're using Rails, you don't need to worry about these details —\neverything should \"just work\".\n\n#### Non-Rails projects\n\n**What if you are using ActiveModel or ActiveRecord outside of Rails, however,\nand you want to use model matchers in a certain example group?** Then you'll\nneed to manually include the module that holds those matchers into that example\ngroup. For instance, you might have to say:\n\n```ruby\nRSpec.describe MySpecialModel do\n  include Shoulda::Matchers::ActiveModel\n  include Shoulda::Matchers::ActiveRecord\nend\n```\n\nIf you have a lot of similar example groups in which you need to do this, then\nyou might find it more helpful to tag your example groups appropriately, then\ninstruct RSpec to mix these modules into any example groups that have that tag.\nFor instance, you could add this to your `rails_helper.rb`:\n\n```ruby\nRSpec.configure do |config|\n  config.include(Shoulda::Matchers::ActiveModel, type: :model)\n  config.include(Shoulda::Matchers::ActiveRecord, type: :model)\nend\n```\n\nAnd from then on, you could say:\n\n```ruby\nRSpec.describe MySpecialModel, type: :model do\n  # ...\nend\n```\n\n\u003Ca name=\"should-vs-is_expectedto\">\u003C\u002Fa>\n### `should` vs `is_expected.to`\n\nIn this README and throughout the documentation, you'll notice that we use the\n`should` form of RSpec's one-liner syntax over `is_expected.to`. Besides being\nthe namesake of the gem itself, this is our preferred syntax as it's short and\nsweet. But if you prefer to use `is_expected.to`, you can do that too:\n\n```ruby\nRSpec.describe Person, type: :model do\n  it { is_expected.to validate_presence_of(:name) }\nend\n```\n\n### A Note on Testing Style\n\nIf you inspect the source code, you'll notice quickly that `shoulda-matchers`\nis largely implemented using reflections and other introspection methods that\nRails provides. At first sight, this might seem to go against the common\npractice of testing behavior rather than implementation. However, as the\navailable matchers indicate, we recommend that you treat `shoulda-matchers` as\na tool to help you ensure correct configuration and adherence to best practices\nand idiomatic Rails in your models and controllers - especially for aspects\nthat in your experience are often insufficiently tested, such as ActiveRecord\nvalidations or controller callbacks (a.k.a. the \"framework-y\" parts).\n\nFor testing your application's unique business logic, however, we recommend focusing on\nbehavior and outcomes over implementation details. This approach will better support\nrefactoring and ensure that your tests remain resilient to changes in how your code\nis structured. While no generalized testing tool can fully capture the nuances of your\nspecific domain, you can draw inspiration from shoulda-matchers to write custom\nmatchers that align more closely with your application's needs.\n\n## Matchers\n\nHere is the full list of matchers that ship with this gem. If you need details\nabout any of them, make sure to [consult the documentation][rubydocs]!\n\n### ActiveModel matchers\n\n- **[allow_value](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fallow_value_matcher.rb)**\n  tests that an attribute is valid or invalid if set to one or more values.\n  _(Aliased as #allow_values.)_\n- **[have_secure_password](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fhave_secure_password_matcher.rb)**\n  tests usage of `has_secure_password`.\n- **[validate_absence_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_absence_of_matcher.rb)**\n  tests usage of `validates_absence_of`.\n- **[validate_acceptance_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_acceptance_of_matcher.rb)**\n  tests usage of `validates_acceptance_of`.\n- **[validate_confirmation_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_confirmation_of_matcher.rb)**\n  tests usage of `validates_confirmation_of`.\n- **[validate_exclusion_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_exclusion_of_matcher.rb)**\n  tests usage of `validates_exclusion_of`.\n- **[validate_inclusion_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_inclusion_of_matcher.rb)**\n  tests usage of `validates_inclusion_of`.\n- **[validate_length_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_length_of_matcher.rb)**\n  tests usage of `validates_length_of`.\n- **[validate_numericality_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_numericality_of_matcher.rb)**\n  tests usage of `validates_numericality_of`.\n- **[validate_presence_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_presence_of_matcher.rb)**\n  tests usage of `validates_presence_of`.\n- **[validate_comparison_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_model\u002Fvalidate_comparison_of_matcher.rb)**\n  tests usage of `validates_comparison_of`.\n\n### ActiveRecord matchers\n\n- **[accept_nested_attributes_for](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Faccept_nested_attributes_for_matcher.rb)**\n  tests usage of the `accepts_nested_attributes_for` macro.\n- **[belong_to](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fassociation_matcher.rb)**\n  tests your `belongs_to` associations.\n- **[define_enum_for](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fdefine_enum_for_matcher.rb)**\n  tests usage of the `enum` macro.\n- **[have_and_belong_to_many](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fassociation_matcher.rb)**\n  tests your `has_and_belongs_to_many` associations.\n- **[have_delegated_type](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fassociation_matcher.rb#L687)**\n  tests usage of the `delegated_type` macro.\n- **[have_db_column](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fhave_db_column_matcher.rb)**\n  tests that the table that backs your model has a specific column.\n- **[have_db_index](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fhave_db_index_matcher.rb)**\n  tests that the table that backs your model has an index on a specific column.\n- **[have_implicit_order_column](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fhave_implicit_order_column.rb)**\n  tests usage of `implicit_order_column`.\n- **[have_many](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fassociation_matcher.rb#L328)**\n  tests your `has_many` associations.\n- **[have_many_attached](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fhave_attached_matcher.rb)**\n  tests your `has_many_attached` associations.\n- **[have_one](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fassociation_matcher.rb#L598)**\n  tests your `has_one` associations.\n- **[have_one_attached](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fhave_attached_matcher.rb)**\n  tests your `has_one_attached` associations.\n- **[have_readonly_attribute](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fhave_readonly_attribute_matcher.rb)**\n  tests usage of the `attr_readonly` macro.\n- **[have_rich_text](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fhave_rich_text_matcher.rb)**\n  tests your `has_rich_text` associations.\n- **[serialize](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fserialize_matcher.rb)** tests\n  usage of the `serialize` macro.\n- **[validate_uniqueness_of](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fvalidate_uniqueness_of_matcher.rb)**\n  tests usage of `validates_uniqueness_of`.\n- **[normalize](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fnormalize_matcher.rb)** tests\n  usage of the `normalize` macro\n- **[encrypt](lib\u002Fshoulda\u002Fmatchers\u002Factive_record\u002Fencrypt_matcher.rb)**\n  tests usage of the `encrypts` macro.\n\n### ActionController matchers\n\n- **[filter_param](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Ffilter_param_matcher.rb)**\n  tests parameter filtering configuration.\n- **[permit](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Fpermit_matcher.rb)** tests\n  that an action restricts the `params` hash.\n- **[redirect_to](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Fredirect_to_matcher.rb)**\n  tests that an action redirects to a certain location.\n- **[render_template](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Frender_template_matcher.rb)**\n  tests that an action renders a template.\n- **[render_with_layout](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Frender_with_layout_matcher.rb)**\n  tests that an action is rendered with a certain layout.\n- **[rescue_from](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Frescue_from_matcher.rb)**\n  tests usage of the `rescue_from` macro.\n- **[respond_with](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Frespond_with_matcher.rb)**\n  tests that an action responds with a certain status code.\n- **[route](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Froute_matcher.rb)** tests\n  your routes.\n- **[set_session](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Fset_session_matcher.rb)**\n  makes assertions on the `session` hash.\n- **[set_flash](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Fset_flash_matcher.rb)**\n  makes assertions on the `flash` hash.\n- **[use_after_action](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Fcallback_matcher.rb#L29)**\n  tests that an `after_action` callback is defined in your controller.\n- **[use_around_action](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Fcallback_matcher.rb#L75)**\n  tests that an `around_action` callback is defined in your controller.\n- **[use_before_action](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Fcallback_matcher.rb#L4)**\n  tests that a `before_action` callback is defined in your controller.\n\n### Routing matchers\n\n- **[route](lib\u002Fshoulda\u002Fmatchers\u002Faction_controller\u002Froute_matcher.rb)** tests\n  your routes.\n\n### Independent matchers\n\n- **[delegate_method](lib\u002Fshoulda\u002Fmatchers\u002Findependent\u002Fdelegate_method_matcher.rb)**\n  tests that an object forwards messages to other, internal objects by way of\n  delegation.\n\n## Extensions\n\nOver time our community has created extensions to Shoulda Matchers. If you've\ncreated something that you want to share, please [let us know][new-issue]!\n\n- **[shoulda-matchers-cucumber]** – Adds support for using Shoulda Matchers in\n  Cucumber tests.\n\n[new-issue]: https:\u002F\u002Fgithub.com\u002Fthoughtbot\u002Fshoulda-matchers\u002Fissues\u002Fnew\n[shoulda-matchers-cucumber]: https:\u002F\u002Fgithub.com\u002Fmajioa\u002Fshoulda-matchers-cucumber\n\n## Contributing\n\nHave a fix for a problem you've been running into or an idea for a new feature\nyou think would be useful? Take a look at the [Contributing\ndocument](CONTRIBUTING.md) for instructions on setting up the repo on your\nmachine, understanding the codebase, and creating a good pull request.\n\n## Compatibility\n\nShoulda Matchers is tested and supported against Ruby 3.2+, Rails\n7.1+, RSpec 3.x, and Minitest 5.x.\n\n- For Ruby \u003C 2.4 and Rails \u003C 4.1 compatibility, please use [v3.1.3][v3.1.3].\n- For Ruby \u003C 3.0 and Rails \u003C 6.1 compatibility, please use [v4.5.1][v4.5.1].\n- For Rails \u003C 7.1 compatibility, please use [v6.5.0][v6.5.0].\n\n[v3.1.3]: https:\u002F\u002Fgithub.com\u002Fthoughtbot\u002Fshoulda-matchers\u002Ftree\u002Fv3.1.3\n[v4.5.1]: https:\u002F\u002Fgithub.com\u002Fthoughtbot\u002Fshoulda-matchers\u002Ftree\u002Fv4.5.1\n[v6.5.0]: https:\u002F\u002Fgithub.com\u002Fthoughtbot\u002Fshoulda-matchers\u002Ftree\u002Fv6.5.0\n\n## Versioning\n\nShoulda Matchers follows Semantic Versioning 2.0 as defined at\n\u003Chttps:\u002F\u002Fsemver.org>.\n\n## Team\n\nShoulda Matchers is currently maintained by [Pedro Paiva][VSPPedro] and [Matheus\nSales][matsales28]. Previous maintainers include [Elliot Winkler][mcmire],\n[Gui Albuk][guialbuk], [Jason Draper][drapergeek], [Melissa Xie][mxie],\n[Gabe Berke-Williams][gabebw], [Ryan McGeary][rmm5t], [Joe Ferris][jferris], and\n[Tammer Saleh][tammersaleh].\n\n[VSPPedro]: https:\u002F\u002Fgithub.com\u002FVSPPedro\n[matsales28]: https:\u002F\u002Fgithub.com\u002Fmatsales28\n[mcmire]: https:\u002F\u002Fgithub.com\u002Fmcmire\n[guialbuk]: https:\u002F\u002Fgithub.com\u002Fguialbuk\n[drapergeek]: https:\u002F\u002Fgithub.com\u002Fdrapergeek\n[mxie]: https:\u002F\u002Fgithub.com\u002Fmxie\n[gabebw]: https:\u002F\u002Fgithub.com\u002Fgabebw\n[rmm5t]: https:\u002F\u002Fgithub.com\u002Frmm5t\n[jferris]: https:\u002F\u002Fgithub.com\u002Fjferris\n[tammersaleh]: https:\u002F\u002Fgithub.com\u002Ftammersaleh\n\n## Copyright\u002FLicense\n\nShoulda Matchers is copyright © Tammer Saleh and [thoughtbot,\ninc][thoughtbot-website]. It is free and open-source software and may be\nredistributed under the terms specified in the [LICENSE](LICENSE) file.\n\n[thoughtbot-website]: https:\u002F\u002Fthoughtbot.com\n\n\u003C!-- START \u002Ftemplates\u002Ffooter.md -->\n## About thoughtbot\n\n![thoughtbot](https:\u002F\u002Fthoughtbot.com\u002Fthoughtbot-logo-for-readmes.svg)\n\nThis repo is maintained and funded by thoughtbot, inc.\nThe names and logos for thoughtbot are trademarks of thoughtbot, inc.\n\nWe love open source software!\nSee [our other projects][community].\nWe are [available for hire][hire].\n\n[community]: https:\u002F\u002Fthoughtbot.com\u002Fcommunity?utm_source=github\n[hire]: https:\u002F\u002Fthoughtbot.com\u002Fhire-us?utm_source=github\n\n\u003C!-- END \u002Ftemplates\u002Ffooter.md -->\n","Shoulda Matchers 是一个用于简化 Rails 项目测试的 Ruby 库，它提供了与 RSpec 和 Minitest 兼容的一行测试代码。通过使用 Shoulda Matchers，开发者可以快速编写针对常见 Rails 功能（如 ActiveModel、ActiveRecord 和 ActionController）的简洁而强大的测试用例，避免了手动编写冗长且容易出错的测试代码。其核心功能包括提供一系列预定义的匹配器，这些匹配器能够直接应用于模型、控制器及路由等组件的测试中，极大地提高了开发效率和代码质量。适用于任何需要对 Rails 应用进行单元或集成测试的场景，特别是当团队追求高效且可维护的测试实践时。",2,"2026-06-11 03:14:38","top_language"]