shoulda-matchers pins 8.0 in its own snippet and leaves the compatibility table to a link
Simple one-liner tests for common Rails functionality
At a glance
- What is it?
- A thoughtbot gem that gives RSpec and Minitest one-line matchers for ActiveRecord associations, ActiveModel validations, controllers, routes and Rails features such as `delegate`. The configuration is four near-identical blocks, the routing matchers are marked RSpec only, and the file stops before the compatibility section its own table of contents promises.
- Who is it for?
- shoulda-matchers earns a place in a Rails test suite that already has RSpec or Minitest wired up: the configure block is four lines, the matchers span five families, and the association and validation scaffolding it removes is the tedious part to write by hand.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- thoughtbot/shoulda-matchers3,578 ★ · -3
| Repository | 2026-09-24 | 2026-11-13 |
|---|---|---|
| thoughtbot/shoulda-matchers | 3.6k | 3.6k |
-3 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Four configure blocks that differ by two words
The getting started pages give four configure blocks, and three variables separate them: the file the block goes in, the framework name, and which libraries get named. On a Rails app with RSpec the block goes at the bottom of `spec/rails_helper.rb`:
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
endOff Rails, where ActiveRecord or ActiveModel is used without Rails itself, the block moves to `spec/spec_helper.rb` and the libraries get named one at a time:
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
endThe Minitest pair repeats both shapes with `:minitest` and `test/test_helper.rb`. `with.library :rails` is the single line that pulls in models, controllers and routes together; outside Rails you pay a line per library, and the comment inside the snippet says exactly that. Before any of it, the gem asks for two things: which matchers you want, and which framework you use so it can make them available.
Routing matchers are the one family RSpec keeps
The usage section sorts matchers by where they sit in an application: database models backed by ActiveRecord, non-database models and form objects backed by ActiveModel, controllers, routes marked RSpec only, and Rails specific features such as `delegate` filed under the heading Independent matchers. The table of contents names the same five families in the same order, as ActiveModel, ActiveRecord, ActionController, Routing and Independent. Routing is the odd one out, and the file admits it in a parenthetical next to the bullet rather than in a paragraph of its own, so a Minitest project configures the gem exactly like an RSpec one and discovers the gap later. Independent is the other odd name. The remaining four are layers of the framework, while this one is a bag of features, which puts `delegate` next to a controller matcher instead of next to anything else in the same abstraction.
Every matcher reads subject, so overriding it changes what is checked
For both RSpec and Shoulda, `subject` is an implicit reference to the object under test, and because the matchers are written as the `should` macro, all of them make use of `subject` internally when they run. A subject is always set automatically by the test framework in any given test case, and overriding it is a choice the file presents as a choice: in certain cases it can be advantageous. The case given is validations, where it is customary to provide a valid model instead of a fresh one, and the example builds that model with FactoryBot while noting you could use anything. That puts the subject inside the matcher's contract rather than beside it. If the subject is not the object you meant to build, the matcher still runs, quietly, against the one you got.
The compatibility and versioning sections exist only as links
The table of contents promises eleven sections after Usage: the availability of RSpec matchers in example groups, `should` against `is_expected.to`, a note on testing style, the matcher families, Extensions, Contributing, Compatibility, Versioning, Team, Copyright and License, and a closing note about thoughtbot. The file gets through the second of those and stops inside the subject example, at `subject { build(`, with nothing further. Compatibility and Versioning are the two sections a reader writing a Gemfile would want most, and neither is here. What the file does say about versions is the constraint in its own install snippet. The rest is reachable elsewhere: the Quick links row points at the hosted documentation on matchers.shoulda.io and at CHANGELOG.md for what changed recently, and both CHANGELOG.md and docs/ sit in the repository. A table of contents that outruns its own file is normal for a README, but this one skips the parts that move with every release.
v8.0.0 and v8.0.1 shipped on the same afternoon
The install snippet hard-codes a major version: `gem 'shoulda-matchers', '~> 8.0'` inside `group :test`, and the Minitest route offers the umbrella gem at `gem 'shoulda', '~> 4.0'`. The release list shows v8.0.0 published on 2026-06-12 at 14:47, v8.0.1 the same day at 18:36, and v7.0.1 back on 2025-10-31. So the constraint quoted in the README is the current series, and a project that copies the snippet lands on 8.0.x. The last push is dated 2026-08-10, two months after v8.0.1, so the default branch `main` carries work that is newer than the newest tag. A Gemfile written from the README takes none of it, and it will need editing by hand when the next major version appears, because nothing in the snippet says how to follow the series instead.
A Python version file next to the Ruby ones
The repository root holds thirty entries. Three of them pin an interpreter: `.ruby-version`, `.tool-versions` and `.python-version`, the last of them in a gem whose language is Ruby. Dependency sets are also files here, with `Gemfile.lock` checked in beside `Appraisals` and a `gemfiles/` directory. Documentation tooling is configured in three places, `.yardopts`, `doc_config/` and `docs/`, and the gem tests itself from `spec/`. Then there are the process files: `Rakefile`, `bin/`, `script/`, `tasks/`, and a `zeus.json` whose name is the only clue to what it configures. Two more root files are named after actions rather than after the library, `REPRODUCTION_SCRIPT.rb` and `custom_plan.rb`. Governance has four files of its own, `CODEOWNERS`, `CONTRIBUTING.md`, `MAINTAINING.md` and `SECURITY.md`. None of these is mentioned anywhere in the README, which reaches the reader through a hosted documentation site instead.
Three assertions, four lines, and nothing about the schema
The worked example is a `MenuItem` model test, given twice so the two frameworks can be read side by side. One line asserts the association to `category` and pins its class name to `MenuCategory`. One asserts presence of `name`. One asserts uniqueness of `name` scoped to `category_id`. That is three assertions, written once as `it { should ... }` and once as `should ...` inside a `context`. Nothing in the example says what `category_id` holds, what the uniqueness scope is anchored on, or what happens when a matching row already exists. The Minitest half stops in the middle of its last line, at `should validate_uniqueness_of(:name).scoped_to(:catego`. The claim these lines stand in for is in the opening paragraph: written by hand, the same tests would be much longer, more complex and error-prone. On length that is fair. The assertions behind the shorthand are still yours to write, and the object they run against is whatever `subject` resolves to.
Editorial conclusion
shoulda-matchers earns a place in a Rails test suite that already has RSpec or Minitest wired up: the configure block is four lines, the matchers span five families, and the association and validation scaffolding it removes is the tedious part to write by hand. It is a weaker fit for a project that needs routing matchers under Minitest, which the file marks as RSpec only, or that wants a stated Rails compatibility range in the README itself, since the Compatibility and Versioning sections are linked from the table of contents and not present in the file. Before pinning a Gemfile, check the release tag against your Rails version: the install snippet hard-codes `~> 8.0` while the default branch has moved past v8.0.1. The gem is MIT licensed.
Frequently asked questions
Does shoulda-matchers work with Minitest?
Yes. The file gives two Minitest configure blocks, one for the umbrella Shoulda gem at `~> 4.0` and one for shoulda-matchers itself at `~> 8.0`, both placed at the bottom of `test/test_helper.rb` with `with.test_framework :minitest`. The one exception it names is routing, which is marked RSpec only.
What does shoulda-matchers need before it does anything?
Two pieces of information: which matchers you want to use, and which test framework you use so it can make them available in your example groups or test case classes. On a Rails app that is a four line block with `with.library :rails`; off Rails the libraries are listed individually with `with.library :active_record` and `with.library :active_model`.
Which version of shoulda-matchers does the README ask for?
The install snippet is `gem 'shoulda-matchers', '~> 8.0'` inside a `group :test` block. Releases show v8.0.1 and v8.0.0 published on 2026-06-12, and v7.0.1 published on 2025-10-31.
What kinds of things do shoulda-matchers matchers cover?
Five families, in the order the table of contents lists them: ActiveModel, ActiveRecord, ActionController, Routing and Independent. ActiveRecord covers database models, ActiveModel covers non-database models and form objects, and the Independent family covers Rails specific features such as `delegate`.
Why does subject matter when using shoulda-matchers?
Every matcher makes use of `subject` internally when it runs, and `subject` is the test framework's implicit reference to the object under test. The file notes a subject is always set automatically, and that overriding it can help, giving the example of supplying a valid model built with FactoryBot when testing validations instead of a fresh one.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thoughtbot-shoulda-matchers)Star history chart for your README
[](https://hysenlabs.com/projects/thoughtbot-shoulda-matchers)Was this useful?