Include original index in extraneous item failure messages by dennisdoomen · Pull Request #3203 · fluentassertions/fluentassertions · GitHub
Skip to content

Include original index in extraneous item failure messages#3203

Merged
dennisdoomen merged 11 commits into
mainfrom
dennisdoomen/fix-extra-item-index-985
May 6, 2026
Merged

Include original index in extraneous item failure messages#3203
dennisdoomen merged 11 commits into
mainfrom
dennisdoomen/fix-extra-item-index-985

Conversation

@dennisdoomen

@dennisdoomen dennisdoomen commented May 3, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #985.

When BeEquivalentTo fails because the subject collection has more items than expected, the error message now includes the original index of each extraneous item, making it easier to diagnose where collections diverge.

Before

Expected actual to contain exactly one item, but found one extraneous item Customer { Age = 16, ... }

After

Single extra item:

Expected actual to contain exactly one item, but found one extraneous item at index 1: Customer { Age = 16, ... }

Multiple extra items:

Expected actual to contain exactly 2 items, but found extraneous items: Customer { Age = 16, ... } (at index 2), Customer { Age = 21, ... } (at index 4)

Implementation

  • EnumerableEquivalencyValidator: subjects are now wrapped in List<IndexedItem<object>> so original indices survive through the matching pipeline to the reporting stage.
  • StrictlyOrderedEquivalencyStrategy / LooselyOrderedEquivalencyStrategy: updated to pass List<IndexedItem<object>> instead of List<object> for subjects; raw objects are accessed via .Item.
  • When only extra items exist, each item is reported with at index N:. When both missing and extra items exist, the compact fallback format is used to avoid expensive per-item formatting.
  • Braces in formatted item strings (e.g. Customer { Age = 16 }) are escaped before embedding into the FailWith format string to prevent string.Format treating them as placeholders.

Copilot AI review requested due to automatic review settings May 3, 2026 10:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves BeEquivalentTo failure diagnostics for collection equivalency by including the original index of extraneous (subject-only) items in the assertion failure message (Fixes #985), making it easier to locate where collections diverge.

Changes:

  • Wrap subjects in IndexedItem<object> so original indices survive the matching pipeline through to reporting.
  • Update ordered equivalency strategies to operate on List<IndexedItem<object>> and compare via .Item.
  • Extend/adjust specs and release notes to cover and document the improved failure messages.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/_pages/releases.md Adds a release note describing the improved BeEquivalentTo extraneous-item messaging.
Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs Updates an existing assertion message expectation to match the new output shape.
Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs Updates single-extra-item expectation and adds coverage for multiple extra items including indices.
Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs Updates golden failure-message text to include the extraneous item index.
Src/FluentAssertions/Equivalency/Steps/StrictlyOrderedEquivalencyStrategy.cs Changes subject inputs to IndexedItem<object> and compares using .Item.
Src/FluentAssertions/Equivalency/Steps/LooselyOrderedEquivalencyStrategy.cs Changes subject inputs to IndexedItem<object> and compares using .Item throughout matching/scoring.
Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs Wraps subjects with original indices and updates failure reporting to include indices for extraneous-only cases.

Comment thread Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs Outdated
Comment thread Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs Outdated
@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown

Test Results

    37 files  ± 0      37 suites  ±0   2m 47s ⏱️ -1s
 6 385 tests + 2   6 384 ✅ + 2  1 💤 ±0  0 ❌ ±0 
39 656 runs  +12  39 650 ✅ +12  6 💤 ±0  0 ❌ ±0 

Results for commit 6059560. ± Comparison against base commit fb42954.

This pull request removes 10 and adds 10 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.Equivalency.Specs.CollectionSpecs ‑ When_the_subject_contains_multiple_extra_items_it_should_include_the_index_of_each
FluentAssertions.Equivalency.Specs.CollectionSpecs ‑ When_the_subject_contains_multiple_extra_items_the_failure_message_should_include_their_formatted_properties
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetPositionExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetPositionExceptionMessage'.)
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.
Object name: 'GetLengthExceptionMessage'.)
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.
Object name: 'GetLengthExceptionMessage'.)
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: 'GetPositionExceptionMessage'.)
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: 'GetPositionExceptionMessage'.)

♻️ This comment has been updated with latest results.

@coveralls

coveralls commented May 3, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 25427286962

Coverage decreased (-0.002%) to 97.14%

Details

  • Coverage decreased (-0.002%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (31 of 32 lines covered, 96.88%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
Src/FluentAssertions/Equivalency/Steps/LooselyOrderedEquivalencyStrategy.cs 13 12 92.31%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 13406
Covered Lines: 13181
Line Coverage: 98.32%
Relevant Branches: 4392
Covered Branches: 4108
Branch Coverage: 93.53%
Branches in Coverage %: Yes
Coverage Strength: 67966.14 hits per line

💛 - Coveralls

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown

@dennisdoomen dennisdoomen force-pushed the dennisdoomen/fix-extra-item-index-985 branch from f29c4ec to 3fc7825 Compare May 3, 2026 15:51
@dennisdoomen dennisdoomen requested a review from jnyrup May 5, 2026 14:06
Comment thread Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs Outdated
Comment thread Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs Outdated

@jnyrup jnyrup left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment thread Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs Outdated
Comment thread Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs Outdated
dennisdoomen and others added 10 commits May 6, 2026 10:48
When BeEquivalentTo fails because the subject collection has more items
than expected, the error message now includes the original index of each
extraneous item to help diagnose where collections diverge.

Before:
  found one extraneous item Customer { Age = 16, ... }

After:
  found one extraneous item at index 1 Customer { Age = 16, ... }

For multiple extra items:
  found extraneous items {Customer { Age = 16, ... } (at index 1), ...}

Fixes #985

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Escape { and } in Formatter.ToString output before embedding into
  the FailWith format string, preventing string.Format from treating
  object dumps like 'Customer { Age = 16 }' as format placeholders
- Pass a single item (not a 1-element list) as the {1} argument
  whenever remainingSubjects.Count == 1, matching the previous
  behavior for the 'both missing and extra' fallback path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Multi-item: 'found extraneous items: A (at index 3), B (at index 96)'
  instead of 'found extraneous items {A (at index 3), B (at index 96)}'
- Single-item: 'found one extraneous item at index 1: X'
  instead of 'found one extraneous item at index 1 X'
- Fix TestBeEquivalent to be a proper test with a throw assertion
  and a descriptive name

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lidator.cs

Co-authored-by: Jonas Nyrup <jnyrup@users.noreply.github.com>
Without escaping curly braces in the formatted items string, string.Format
throws a FormatException, causing a **WARNING** fallback instead of the
actual failure message. The existing test didn't catch this because the
WARNING still contained the expected text from the raw format string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jonas Nyrup <jnyrup@users.noreply.github.com>
…lidator.cs

Co-authored-by: Jonas Nyrup <jnyrup@users.noreply.github.com>
@dennisdoomen dennisdoomen force-pushed the dennisdoomen/fix-extra-item-index-985 branch from 510afe6 to ad34005 Compare May 6, 2026 08:52
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Jun 29, 2026
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.

collection Should().BeEquivalentTo() vs position of first difference

4 participants