Skip to content

feat: add Offside.Testing assertions package - #16

Merged
vpcmps merged 1 commit into
masterfrom
feat/offside-testing
Aug 26, 2026
Merged

vpcmps merged 1 commit into
masterfrom
feat/offside-testing

Conversation

@vpcmps

@vpcmps vpcmps commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Adds Offside.Testing: fluent assertions over Result, Result<T>, Error, and JSON message catalogs.

The point is not shorter test code — it is what you read when a test fails. Assert.True(result.IsFailure) reports Assert.True() Failure; this reports which errors the result actually carried.

Design decisions

  • No FluentAssertions dependency. FluentAssertions 8+ is commercially licensed, and an Offside.FluentAssertions package would push that onto consumers. Failures throw OffsideAssertionException; xUnit, NUnit, MSTest and TUnit all report any exception as a failed test, so the package works in all four with zero test-framework references.
  • ShouldHaveError, not Should(). Deliberately avoids colliding with FluentAssertions' and Shouldly's entry point, so both can be used in the same file.
  • The error is located first, then refined. ShouldHaveError("x").WithKind(k) can say "the error exists, but its kind is Conflict, expected Validation" instead of "no error matched".
  • OffsideCatalog reads the JSON directly rather than going through IErrorMessageResolver, whose fallback returns Error.Code on a miss — that makes a missing code indistinguishable from a template equal to the code. Reading the catalog also enables coverage assertions no resolver could support.
  • No changes to the core. Extending IErrorMessageResolver with TryGetMessage was considered and rejected: breaking for external implementors, and ISP-wise it forces every implementation to support a method only tests need.

API

Assertion Means
ShouldBeSuccess() Succeeded. On Result<T>, exposes the value via Subject / WithValue.
ShouldBeFailure() Failed, without saying how.
ShouldHaveError(code) Failed carrying this error, ignoring others. Default choice.
ShouldHaveOnlyError(code) Failed carrying this error and nothing else.
ShouldHaveErrorsInOrder(codes) Carries exactly these codes, in this order.
ShouldHaveErrorCount(n) Carries this many errors.

Refinements: WithKind, WithErrorCode, ForField, WithArgument, WithMessage(resolver, text). .And returns the result for chaining and is always optional.

Catalog: ShouldDefine, ShouldDefineAll, ShouldResolve (code exists and no {token} left unfilled), ShouldResolveAll, ShouldDefineSameCodesAs (keeps a translated catalog from drifting behind the invariant one).

ShouldHaveErrorsInOrder is documented as the assertion to avoid by default: order comes from Result.Combine argument order or FluentValidation rule declaration order, so reordering rules breaks it without any behaviour changing.

Also included

  • offside-testing agent skill, installed by offside init alongside the existing nine, with a what to cover section. Cross-referenced from offside-implementation and offside-refactoring.
  • The repository's own tests migrated off Assert.True(result.IsFailure) — dogfooding.
  • Bilingual guide at docs/testing.md / docs/pt-BR/testing.md, plus README, API reference, CLI docs and CHANGELOG.

Testing

Offside.Testing.Tests uses raw xUnit on purpose: asserting the package with the package would let a "never fails" defect pass unnoticed. Every negative case asserts the message text, since the diagnostics are the product here.

dotnet build and dotnet test pass across the solution on net8.0 and net10.0, 0 warnings, 0 errors. The package itself also builds for netstandard2.0.

🤖 Generated with Claude Code


Note

Low Risk
Additive test-only package and documentation; core runtime APIs are unchanged, with risk limited to consumers adopting the new assertion surface.

Overview
Introduces Offside.Testing, a new optional NuGet package with fluent assertions for Result, Result<T>, Error, and JSON message catalogs. Failures throw OffsideAssertionException (no xUnit/NUnit dependency); APIs use ShouldHaveError / ShouldBeSuccess instead of Should() so they can sit beside FluentAssertions or Shouldly. ErrorAssertion locates an error by code first, then refines with WithKind, ForField, WithArgument, and WithMessage. OffsideCatalog loads catalog JSON directly for ShouldDefine, ShouldResolve (unfilled {token} detection), and ShouldDefineSameCodesAs across cultures.

The solution adds Offside.Testing and Offside.Testing.Tests; several existing test projects reference the package and replace Assert.True(result.IsFailure) with the new extensions. offside init now installs a tenth skill (offside-testing), with SkillInstaller and implementation/refactoring skills updated accordingly. Docs and changelog cover install, API reference, and bilingual docs/testing.md / docs/pt-BR/testing.md.

Reviewed by Cursor Bugbot for commit bffd7ef. Bugbot is set up for automated code reviews on this repo. Configure here.

Add Offside.Testing: fluent assertions over Result, Result<T>, Error and
JSON message catalogs, with no test-framework dependency. Failures throw
OffsideAssertionException, which xUnit, NUnit, MSTest and TUnit all report
as a failed test.

Entry points are named ShouldHaveError rather than Should(), so the package
coexists with FluentAssertions or Shouldly in the same file. Every failure
message carries the actual contents of the subject, which is the point:
Assert.True(result.IsFailure) reports nothing about which errors were
carried.

OffsideCatalog reads catalogs directly instead of going through
IErrorMessageResolver, whose fallback returns Error.Code on a miss and makes
a missing code indistinguishable from a template equal to the code. That
also enables coverage assertions: unfilled {token} detection and comparing a
translated catalog against the invariant one.

Ship an offside-testing agent skill via offside init, migrate the
repository's own tests off Assert.True(result.IsFailure), and document the
package in both languages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bf3eb74d-6022-4823-bdef-ed11084c83e4


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Aug 26, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_0bfd0aea-e299-4e71-b184-b65d03ec7097)

@vpcmps
vpcmps merged commit 66ca754 into master Aug 26, 2026
9 checks passed
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