Visitar URL original
Add public ValueTypeAssertions<TSubject, TAssertions> base class by dennisdoomen · Pull Request #3308 · fluentassertions/fluentassertions · GitHub
Skip to content

Add public ValueTypeAssertions<TSubject, TAssertions> base class - #3308

Merged
dennisdoomen merged 2 commits into
mainfrom
dennisdoomen-value-type-assertions-base-class
Aug 19, 2026
Merged

dennisdoomen merged 2 commits into
mainfrom
dennisdoomen-value-type-assertions-base-class

Conversation

@dennisdoomen

Copy link
Copy Markdown
Member

Closes #3292

What

Adds a public ValueTypeAssertions<TSubject, TAssertions> base class in FluentAssertions.Primitives, giving third-party assertion authors a ready-made base for value types (structs), mirroring what ReferenceTypeAssertions<TSubject, TAssertions> already offers for reference types.

API

public abstract class ValueTypeAssertions<TSubject, TAssertions>
    where TSubject : struct
    where TAssertions : ValueTypeAssertions<TSubject, TAssertions>
{
    protected ValueTypeAssertions(TSubject? subject, AssertionChain assertionChain);

    public TSubject? Subject { get; }
    public AssertionChain CurrentAssertionChain { get; }
    protected abstract string Identifier { get; }

    public AndConstraint<TAssertions> Be(TSubject expected, string because = "", params object[] becauseArgs);
    public AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = "", params object[] becauseArgs);
    public AndConstraint<TAssertions> BeNull(string because = "", params object[] becauseArgs);
    public AndConstraint<TAssertions> NotBeNull(string because = "", params object[] becauseArgs);
    public AndConstraint<TAssertions> Match(Expression<Func<TSubject, bool>> predicate, string because = "", params object[] becauseArgs);
}

The constructor and CurrentAssertionChain/WithDefaultIdentifier usage follow the same conventions already used by ReferenceTypeAssertions, BooleanAssertions, and the other primitive assertion classes in this codebase, rather than the constructor shape shown in the issue's proposal (which predates the AssertionChain pattern).

Changes

  • Src/FluentAssertions/Primitives/ValueTypeAssertions.cs — new base class.
  • Tests/FluentAssertions.Specs/Primitives/ValueTypeAssertionsSpecs.cs — specs covering Be, NotBe, BeNull, NotBeNull, Match, and the Equals guard, using a Coordinates struct/CoordinatesAssertions test fixture.
  • Tests/Approval.Tests/ApprovedApi/**/*.verified.txt — updated via AcceptApiChanges.ps1 for the new public API.
  • docs/_pages/extensibility.md — mentions the new base class alongside ReferenceTypeAssertions.
  • docs/_pages/releases.md — release note entry.

Testing

  • dotnet test Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj --filter "FullyQualifiedName~Primitives" — all pass (net47/net6.0/net8.0).
  • dotnet test Tests/Approval.Tests/Approval.Tests.csproj — all pass after accepting the API change.

@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Test Results

    37 files  ±  0      37 suites  ±0   2m 40s ⏱️ -2s
 6 515 tests + 40   6 514 ✅ + 40  1 💤 ±0  0 ❌ ±0 
40 436 runs  +240  40 430 ✅ +240  6 💤 ±0  0 ❌ ±0 

Results for commit fb53fe6. ± Comparison against base commit 62a8c70.

This pull request removes 10 and adds 48 tests. Note that renamed tests count towards both.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
Object name: 'GetPositionExceptionMessage'.)
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_null_value_equals_a_null_expected_value
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_null_value_is_never_equal_to_the_unexpected_value
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_null_value_never_differs_from_a_null_unexpected_value
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_null_value_never_equals_the_expected_value
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_null_value_never_satisfies_the_predicate
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_value_must_equal_the_expected_value
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_value_must_not_equal_the_unexpected_value
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_value_must_satisfy_the_nullable_predicate
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_value_must_satisfy_the_predicate
FluentAssertions.Specs.Primitives.NullableValueTypeAssertionsSpecs ‑ A_value_that_has_not_been_initialized_is_not_allowed_to_be_not_null
…

♻️ This comment has been updated with latest results.

@coveralls

coveralls commented Aug 10, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 32225735660

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage decreased (-0.03%) to 97.166%

Details

  • Coverage decreased (-0.03%) from the base build.
  • Patch coverage: 67 of 67 lines across 3 files are fully covered (100%).
  • 2 coverage regressions across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

2 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
Src/FluentAssertions/TypeExtensions.cs 2 60.0%

Coverage Stats

Coverage Status
Relevant Lines: 13582
Covered Lines: 13355
Line Coverage: 98.33%
Relevant Branches: 4412
Covered Branches: 4129
Branch Coverage: 93.59%
Branches in Coverage %: Yes
Coverage Strength: 67892.58 hits per line

💛 - Coveralls

@dennisdoomen
dennisdoomen marked this pull request as draft August 10, 2026 16:59
@dennisdoomen dennisdoomen added this to the 8.11 milestone Aug 15, 2026
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from f1a7c19 to 673be2d Compare August 15, 2026 10:10
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from 673be2d to e9db98c Compare August 15, 2026 12:28
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from e9db98c to 6af3dd1 Compare August 15, 2026 12:29
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from 6af3dd1 to 6fd822e Compare August 15, 2026 12:30
@dennisdoomen
dennisdoomen marked this pull request as ready for review August 15, 2026 12:30
@dennisdoomen
dennisdoomen requested a review from jnyrup August 15, 2026 12:31
@github-actions

github-actions Bot commented Aug 15, 2026 •

Copy link
Copy Markdown

Qodana for .NET

It seems all right 👌

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Contact Qodana team

Contact us at qodana-support@jetbrains.com

@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from 6fd822e to e7ae3ea Compare August 16, 2026 07:10
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from e7ae3ea to dada5dd Compare August 16, 2026 09:30
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from dada5dd to 0120391 Compare August 16, 2026 09:41
@dennisdoomen
dennisdoomen force-pushed the dennisdoomen-value-type-assertions-base-class branch from 0120391 to 6e0154b Compare August 16, 2026 09:46
@dennisdoomen
dennisdoomen requested a review from jnyrup August 16, 2026 11:04
This was referenced Nov 12, 2026

This branch was previously deployed

1 inactive deployment
qodana-pr — fb53fe6a Deployed Aug 19, 2026 by dennisdoomen via Qodana Scan #131
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Public ValueTypeAssertions<TSubject, TAssertion>; base class for custom value-type assertions

3 participants