Skip to content

Add Micro::Case::Success/Failure test-double factories#155

Open
serradura wants to merge 3 commits into
mainfrom
feat/native-test-doubles-for-result
Open

Add Micro::Case::Success/Failure test-double factories#155
serradura wants to merge 3 commits into
mainfrom
feat/native-test-doubles-for-result

Conversation

@serradura

@serradura serradura commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds Micro::Case::Success.new(data:, type:, use_case:) and Micro::Case::Failure.new(...) for fabricating result instances in tests without running a real use case. Success.to_yield(...) / Failure.to_yield(...) return a Micro::Case::Result::Wrapper for stubbing block-form Micro::Case.call(input) { |on| ... } consumers (e.g. RSpec's and_yield, Mocha's yields).
  • Opt-in: the gem does not auto-require these. Add require 'micro/case/with_test_doubles' to spec/spec_helper.rb or test/test_helper.rb to enable them. Production load paths are unaffected.
  • Strictly additive — no existing API moves or changes shape. Calls route through Result#__set__, so the curated Error::InvalidResultType / InvalidResult / InvalidUseCase exceptions still raise on bad input and no-op under config.disable_runtime_checks = true. Constants are modules, not classes — result.class == Micro::Case::Result (no subclass surface).
  • Bundles a runnable examples/test_doubles/ project demonstrating both stubbing shapes with paired RSpec and Minitest+Mocha suites (FetchEmail collaborator + SendInvite return-value consumer + DeliverReferral block-form consumer).
  • Updates CHANGELOG.md (Keep a Changelog) under [Unreleased] and adds a new Testing with test doubles section to README.md + README.pt-BR.md in lockstep. No version bump in this PR — that ships separately when the next release is cut.

Closes #6.

What's new

File Purpose
lib/micro/case/success.rb Micro::Case::Success.new / .to_yield
lib/micro/case/failure.rb Micro::Case::Failure.new / .to_yield
lib/micro/case/with_test_doubles.rb Opt-in entry point users require from their helper
examples/test_doubles/ Runnable RSpec + Minitest+Mocha example project
test/micro/case/success_test.rb, failure_test.rb Coverage of defaults, overrides, validations, transitions, identity, .to_yield wrapper behavior, memoised default use_case:
test/micro/case/disable_runtime_checks_test.rb New tests confirming the factories participate in the disabled-checks toggle

Why opt-in

The factories only make sense in test code. Auto-requiring them adds two new public constants and four module methods to every production process for no production-side benefit. A single require 'micro/case/with_test_doubles' in spec_helper.rb / test_helper.rb keeps the constants undefined elsewhere and matches the gem's existing with_* opt-in convention (cf. with_activemodel_validation).

Why under Micro::Case, not under Micro::Case::Result?

The constants sit directly under Micro::Case (siblings of Micro::Case::Safe and Micro::Case::Result), not nested under Result. Three reasons:

  1. Shorter call sites. Micro::Case::Success.new(...) reads cleaner than Micro::Case::Result::Success.new(...). Tests fabricating results are noisy enough without the extra namespace segment.
  2. Better pattern-matching ergonomics. Future work layering === / deconstruct_keys integration on top of these constants becomes case result in Micro::Case::Success(slug:) — the shorter path matters most in pattern-match arms.
  3. Mirrors solid-result. Its sibling gem exposes Solid::Success / Solid::Failure at the top level of its namespace, not nested under Solid::Result. Aligning layouts removes one cognitive bump for readers moving between the two libraries.

Backward compatibility

  • No existing public constant moves or changes shape.
  • No existing method is touched.
  • Micro::Case::Success / Micro::Case::Failure are new constants — grep -rn confirms neither existed in lib/ or test/ before this PR.
  • User-land shims that monkey-patch Micro::Case::Result with their own Success / Failure modules live at a different namespace path from the new native constants, so the two coexist without collision. Migration is opt-in.
  • Bareword Success / Failure inside call!. When with_test_doubles is loaded, Micro::Case::Success and Micro::Case::Failure exist as constants directly under Micro::Case. Inside a Micro::Case subclass, a bareword Success (no args, no parens) would resolve to the constant, not the producer-side helper method. In practice every realistic call site has arguments (Success(:ok), Success result: {...}, Failure :foo), and Ruby parses those as method calls regardless — so this only matters for the contrived case of a method body whose final expression is the literal token Success or Failure. Both READMEs gain a note explaining the workaround (Success() / Failure() with empty parens).
  • Transitions stay observable (__set_transition runs as before when transitions are enabled).
  • No new internal Micro::Case::Check entries — the factories piggyback on the existing result_type! / result_data! / micro_case_instance! guards via __set__.

Test plan

  • bundle exec rake test822 runs / 8252 assertions / 0 failures (ENABLE_TRANSITIONS=true)
  • ENABLE_TRANSITIONS=false bundle exec rake test561 runs / 7250 assertions / 0 failures
  • examples/test_doubles/ Minitest+Mocha suite — 8 runs / 28 assertions / 0 failures
  • examples/test_doubles/ RSpec suite — 8 examples / 0 failures
  • Smoke test: Micro::Case::Success / Micro::Case::Failure are undefined until require 'micro/case/with_test_doubles', defined after.
  • CI matrix (Ruby × Rails × ENABLE_TRANSITIONS={true,false}).

🤖 Generated with Claude Code

…#6)

`Micro::Case::Result::Success.new(data:, type:, use_case:)` and
`Micro::Case::Result::Failure.new(...)` fabricate result instances in
tests without running a real use case. `Success.to_yield(...)` and
`Failure.to_yield(...)` return a `Micro::Case::Result::Wrapper` for
stubbing block-form `Micro::Case.call(input) { |on| ... }` consumers.

Opt-in: the gem does NOT auto-require the factories. Add
`require 'micro/case/with_test_doubles'` to spec_helper.rb /
test_helper.rb to enable them. Production load paths unaffected.

Strictly additive — calls flow through `Result#__set__`, so the curated
`Error::InvalidResultType` / `InvalidResult` / `InvalidUseCase`
exceptions raise on bad input and no-op under
`config.disable_runtime_checks = true`. Constants are modules, not
classes — `result.class == Micro::Case::Result` (no subclass surface).

Includes a runnable `examples/test_doubles/` project with paired
RSpec and Minitest+Mocha suites demonstrating both return-value
stubbing (`and_return` / `returns`) and block-form stubbing
(`and_yield` / `yields`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@serradura serradura self-assigned this May 27, 2026
@serradura serradura added this to the 5.x milestone May 27, 2026
The new factories ship under the existing 5.7.1 line. CHANGELOG entry
moves to an [Unreleased] section so it can attach to whatever version
ships next, and READMEs / example Gemfile go back to 5.7.x references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@serradura serradura changed the title Add Micro::Case::Result::Success/Failure test-double factories (closes #6) Add Micro::Case::Result::Success/Failure test-double factories May 27, 2026
…ase::{Success,Failure}

Per the updated issue spec, the constants live one segment higher in the
namespace tree:

  - lib/micro/case/result/{success,failure}.rb
  -> lib/micro/case/{success,failure}.rb

  - Micro::Case::Result::{Success,Failure}
  -> Micro::Case::{Success,Failure}

The shorter path reads cleaner at call sites and mirrors the sibling
`solid-result` gem layout (`Solid::Success` / `Solid::Failure`). The
constants remain modules (no subclass surface) and the returned object
is still a plain `Micro::Case::Result`.

Both READMEs gain a brief note about the bareword `Success` / `Failure`
shadowing concern: now that the constants live directly under
`Micro::Case`, a bare-token `Success` or `Failure` inside a `call!`
method body resolves to the constant rather than the helper method.
In practice every realistic call site has args, so Ruby parses them
as method calls regardless.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@serradura serradura changed the title Add Micro::Case::Result::Success/Failure test-double factories Add Micro::Case::Success/Failure test-double factories May 27, 2026
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.

1 participant