Sitelet https://github.com/aasm/aasm/pull/887
Skip to content

Fix must_*/wont_* expectations raising NoMethodError on Minitest 6 - #887

Open
hikmetba-bit wants to merge 1 commit into
aasm:masterfrom
hikmetba-bit:fix/minitest6-expectation-wrapper-875
Open

hikmetba-bit wants to merge 1 commit into
aasm:masterfrom
hikmetba-bit:fix/minitest6-expectation-wrapper-875

Conversation

@hikmetba-bit

Copy link
Copy Markdown

Summary

Fixes #875.

minitest_spec.rb called AASM.infect_an_assertion(...) for each must_*/wont_* pair. infect_an_assertion only ever defines the generated method as an instance method on whatever module it's called on. Under Minitest 5.x, calling it on an arbitrary module (AASM here) also happened to define the real method on Minitest::Expectation — the class that _(obj).must_foo / value(obj).must_foo actually dispatch through — via a second code path that Minitest 5.x's own source explicitly flags as deprecated ("This will fail in Minitest 6"). That path was removed in Minitest 6.0, so AASM.infect_an_assertion alone stopped having any effect on that usage, and every must_*/wont_* assertion raised:

NoMethodError: undefined method 'must_have_state' for an instance of Minitest::Expectation

exactly matching the issue report.

Fix

Call infect_an_assertion on both AASM (preserving whatever Minitest 5.x's deprecated global mechanism still provides there, e.g. bare obj.must_foo — not something restorable here since Minitest 6.0 itself intentionally removed that mechanism, it's not AASM-specific) and directly on Minitest::Expectation, which is what actually needs the method defined on it and works correctly on both Minitest 5.x and 6.x.

Scope note

This fixes the _(obj).must_foo / value(obj).must_foo form — the one Minitest's own deprecation warning recommends migrating to, and the one the reported NoMethodError is actually about (note it names Minitest::Expectation as the receiver, which only happens via that wrapped form). The bare obj.must_foo form used throughout the rest of this test file (and the README) depends on Minitest's own removed global-expectations mechanism and remains unavailable under Minitest 6 — that's a separate, broader migration Minitest itself forced on every one of its users, not something fixable from AASM's side without reintroducing global Object pollution. Flagging it here for visibility rather than trying to paper over it.

Verification

Reproduced the exact reported crash: installed real Minitest 6.0.6 and 5.25.4 in isolated Gemfiles, confirmed _(obj).must_have_state(...) raises the identical NoMethodError on 6.0.6 with the code as-is on main, and that it's fixed after this change — while the existing bare-call tests keep passing unchanged on 5.25.4 (with their pre-existing deprecation warnings, unaffected by this diff) and remain in their pre-existing (unrelated, not regressed) broken state on 6.0.6.

Added a regression describe block to test/unit/minitest_matcher_test.rb covering all four matcher pairs via the _(obj) wrapper. Mutation-tested: reverting the source change makes all 12 tests in the file fail under Minitest 6.0.6 (including the 4 new ones, with the exact reported error); with the fix, the 4 new tests pass under both 5.25.4 and 6.0.6, and the full file remains 12/12 passing under 5.25.4 (0 regressions).

🤖 Generated with Claude Code

minitest_spec.rb called AASM.infect_an_assertion(...) for each
must_*/wont_* pair. infect_an_assertion only ever defines the
generated method as an instance method on whatever module it's
called on. Under Minitest 5.x, calling it on an arbitrary module
(AASM here) also happened to define the real method on
Minitest::Expectation - the class that `_(obj).must_foo` /
`value(obj).must_foo` actually dispatch through - via a second,
explicitly-deprecated code path ("This will fail in Minitest 6").
That path was removed in Minitest 6.0, so AASM.infect_an_assertion
alone stopped having any effect on that usage, and every
must_*/wont_* assertion raised:

  NoMethodError: undefined method 'must_have_state' for an instance
  of Minitest::Expectation

Call infect_an_assertion on both AASM (preserving whatever the
Minitest 5.x deprecated global mechanism still provides, e.g. bare
`obj.must_foo` - not something restorable here since Minitest 6.0
itself intentionally removed that mechanism) and directly on
Minitest::Expectation, which is what actually needs the method
defined on it and works correctly on both Minitest 5.x and 6.x.

Added a regression test suite using the `_(obj).must_foo` wrapper -
the form Minitest's own deprecation warning already recommends -
covering all four matcher pairs. The existing bare-call tests
elsewhere in this file remain as-is; they depend on Minitest's own
removed global-expectations mechanism and are a separate, broader
Minitest 6 migration concern beyond this fix's scope.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NoMethodError: undefined method `must_have_state' for an instance of Minitest::Expectation

1 participant