skip to content

When a library-fine calculator's suite moves from Minitest 5 to Minitest 6, which assertion and spec changes break tests, and how do you fix each?

level: seniorimportance: should knowfreq 28%

answer

  1. MiniTest constant is gone
  2. Object expectations removed
  3. assert_equal nil: failure, not warning
  4. assert_send removed
  5. message procs no longer chained

basics

~10 s

Minitest 6 turned 5.x deprecations into failures: bare obj.must_equal needs _(obj), assert_equal nil needs assert_nil, assert_send is gone, and the MiniTest namespace no longer exists. Fix the warnings on 5.27 first, then upgrade.

solid answer

~40 s

Minitest 6.0 removed what 5.x had deprecated for years. **`MiniTest::Test`** and `MiniTest::Unit::TestCase` raise `NameError`; use `Minitest::Test`. **Expectations on `Object`** are gone, so `fine.must_equal 75` raises `NoMethodError`; wrap it as `_(fine).must_equal 75` (or `value`/`expect`). **`assert_equal nil, x`** now fails with `Use assert_nil if expecting nil.`, and since 6.0.3 `assert_same nil, x` does too. **`assert_send`** was removed in favour of `assert_predicate` or `assert_operator`, and `Minitest::Test#class_name` is gone. Custom assertions that pass a proc to `message` now get only that proc's text. The safe path: run the suite on the last 5.x with warnings visible, fix every deprecation, then bump to 6. Ruby 4.0 bundles 6.0.0, so an unpinned Gemfile can take the jump by accident.

code

ruby · 15 lines
ruby
# Minitest 5 (deprecated forms)
class FineTest < MiniTest::Test
  def test_waiver
    assert_equal nil, calc.waiver_for(member)
    assert_send [member, :suspended?]
  end
end

# Minitest 6
class FineTest < Minitest::Test
  def test_waiver
    assert_nil calc.waiver_for(member)
    assert_predicate member, :suspended?
  end
end

go deeper

for a junior

Recall that Minitest 6 wants _(value).must_equal, assert_nil for nil, and Minitest rather than MiniTest as the constant.

for a middle

Explain which 5.x deprecations became errors or failures in 6.0, and why a bare expectation now raises NoMethodError.

for a senior

Plan the upgrade: pin 5.27, harvest deprecation warnings, rewrite mechanically, then bump, and check custom assertion messages after the proc change.

for a principal

Decide how the organisation sequences Ruby and test-framework upgrades so a bundled-gem bump never lands inside an unrelated Ruby migration.

## Why this upgrade breaks suites Minitest 6.0.0 (December 2025) is a major release whose History calls its removals "deaths in the family". Almost every one had been a deprecation warning in 5.x for years, so a suite that ran clean with warnings visible on 5.27 upgrades quietly, and a suite that ignored warnings fails in dozens of places at once. Ruby 4.0.7 bundles Minitest 6.0.0, so a project that relied on the Ruby-provided Minitest, or never pinned it, can cross the boundary during a Ruby upgrade without anyone choosing to. This answer covers the assertion and spec changes. The same release also changed the runner (opt-in plugins, `MT_CPU` instead of `N`, renamed ordering and filter options) and moved `Minitest::Mock` into the separate minitest-mock gem; those are separate checklists. ## The breaking changes, one by one | Minitest 5 code | Minitest 6 result | fix | |---|---|---| | `class FineTest < MiniTest::Test` | `NameError: uninitialized constant MiniTest` | `Minitest::Test` (lower-case t) | | `calc.fine_for(3).must_equal 75` | `NoMethodError` (Error) | `_(calc.fine_for(3)).must_equal 75` | | `proc { ... }.must_raise ArgumentError` | `NoMethodError` | `_ { ... }.must_raise ArgumentError` | | `assert_equal nil, calc.waiver_for(m)` | Failure: `Use assert_nil if expecting nil.` | `assert_nil calc.waiver_for(m)` | | `assert_same nil, x` (6.0.3+) | Failure, same message | `assert_nil x` | | `assert_send [member, :suspended?]` | `NoMethodError` | `assert_predicate member, :suspended?` or `assert_operator` | | `self.class_name` in a helper | `NoMethodError` | `self.class.name` | Notes on the less obvious rows: - **The nil rule fails even when both sides are nil.** It is a style rule enforced as a failure, not an equality check, so `assert_equal nil, nil` is red. - **`_`, `value` and `expect` are the same method.** Pick one for the codebase; `expect` reads naturally for teams coming from RSpec, but it returns a `Minitest::Expectation`, not an RSpec matcher target. - **Expectations are now thread-safe by construction**, because the wrapper carries the test context instead of looking it up through a thread-local. ## A quieter change in custom assertions In 6.0, when `message` receives a proc, that proc's output **overrides** all other output instead of being chained with the default text. A custom assertion such as ```ruby def assert_capped(calc, days, msg = nil) msg = message(msg) { "fine for #{days} days exceeds cap" } assert_operator calc.fine_for(days), :<=, calc.cap_cents, msg end ``` still works, but a caller who passed `-> { "context" }` as `msg` now sees only `context`, without the generated detail. Failure messages in CI logs can become less informative without any test turning red. ## A migration plan 1. **Pin before you move.** Put `gem "minitest", "~> 5.27"` in the Gemfile so the Ruby upgrade and the Minitest upgrade are separate changes. 2. **Make warnings loud.** `minitest/autorun` enables Ruby deprecation warnings; collect Minitest's deprecation warnings from a full run's log and treat each one as a to-do. 3. **Rewrite mechanically.** The fixes are regular: `MiniTest::` to `Minitest::`, bare `.must_`/`.wont_` calls wrapped in `_()`, `proc { }.must_raise` to `_ { }.must_raise`, `assert_equal nil` to `assert_nil`, `assert_send` to `assert_predicate`. 4. **Bump to `~> 6.0`** and run the suite; anything still failing is either a runner or plugin change or a missing minitest-mock dependency. 5. **Check the message output** of custom assertions by forcing one to fail and reading it. ## What does not change - `Minitest::Test`, `test_` methods, `setup`/`teardown` and the `assert_*`/`refute_*` families (apart from `assert_send`) behave as before. - `describe`, `it`, `let`, `before`, `after` and the expectation names are unchanged; only where expectations are called from changed. - Minitest 6 requires Ruby 3.2 or later, which a Ruby 4.0 project already meets.

  • With Minitest 6, a spec calls calc.fine_for(3).must_equal 75 without a wrapper. Is that a Failure or an Error?
    An Error. `must_equal` no longer exists on `Object`, so Ruby raises `NoMethodError` before any assertion runs, and Minitest reports escaping exceptions as Errors. Wrapping the value, `_(calc.fine_for(3)).must_equal 75`, restores it.
  • Why can upgrading Ruby to 4.0 change the Minitest version a project runs?
    Ruby 4.0 ships Minitest 6.0.0 as a bundled gem. A project that never declared Minitest in its Gemfile, or declared it without a version constraint, can resolve to 6.x after the Ruby upgrade and hit the removals. Pinning `~> 5.27` first keeps the two upgrades separate.

saying these in an interview costs you the question

  • MiniTest::Test still works in Minitest 6 as an alias.
  • Bare obj.must_equal in Minitest 6 only prints a deprecation warning.
  • assert_equal nil, nil passes in Minitest 6 because both sides are nil.
  • Upgrading Ruby cannot change which Minitest version a suite uses.
  • assert_send still exists in Minitest 6 for predicate checks.