question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Enumerable is not reused for assertion check and message

See original GitHub issue

Before you file a bug, have you:

  • Tried upgrading to newest version of Fluent Assertions, to see if your issue has already been resolved and released? - yes
  • Checked existing open and closed issues, to see if the issue has already been reported? - yes could not find it
  • Tried reproducing your problem in a new isolated project? - yes
  • Read the documentation? - yes
  • Considered if this is a general question and not a bug?. For general questions please use StackOverflow. - yes, seems like a bug

Description

Using collection assertion on IEnumerable leads to enumerating the collection twice. Once for the actual assertion check and second time for formatting.

When items are actively added to the enumerable, you might end up with assertion failure that lists correct collection items. This makes the assertion message incorrect and confusing.

E.g. (added newlines for readability):

FluentAssertions.Execution.AssertionFailedException: Expected enumerable
{"a", "b", "c"} 
to contain items 
{"a", "b", "c"} 
in order, but "c" (index 2) did not appear (in the right order).
   at FluentAssertions.Execution.FallbackTestFramework.Throw(String message)

I saw this in real system, where one side is outputting messages and I am adding them into a list as they come, then using LINQ Select to select just the names of those messages and comparing them to what I expect. The server outputs last message and closes, and somehow just at the right time FluentAssertions check the collection, and just before formatting, the last message is output, and I see the error above.

Complete minimal example reproducing the issue

Complete means the code snippet can be copied into a unit test method in a fresh C# project and run. Minimal means it is stripped from code not related to reproducing the issue.

The following example outputs 2 items when first asked for the enumerator, and then all items when asked second time. Simulating what happens when item is added to the enumerable just at the right time. It also writes to console when it is asked for enumerator. E.g.

using FluentAssertions;
using System.Collections;

namespace ConsoleApp38
{
    internal class DoublyEnumerable : IEnumerable<string>
    {
        private readonly List<string> _collection;
        private bool _called;

        public DoublyEnumerable()
        {
            _collection = new List<string> { "a", "b", "c" };
        }
        public IEnumerator<string> GetEnumerator()
        {
            Console.WriteLine("GetEnumerator called");
            if (!_called)
            {
                _called = true;
                return _collection.Take(2).GetEnumerator();
            }

            return _collection.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            try {
                var enumerable = new DoublyEnumerable();
                enumerable.Should().ContainInOrder(new[] { "a", "b", "c" });
                Console.WriteLine("it passed");
            }
            catch  (Exception ex){
                Console.WriteLine(ex);
            }
        }
    }
}
Output:
GetEnumerator called
GetEnumerator called
FluentAssertions.Execution.AssertionFailedException: Expected enumerable {"a", "b", "c"} 
to contain items {"a", "b", "c"} in order, but "c" (index 2) did not appear (in the right order).
   at FluentAssertions.Execution.FallbackTestFramework.Throw(String message)
   at FluentAssertions.Execution.TestFrameworkProvider.Throw(String message)
   at FluentAssertions.Execution.DefaultAssertionStrategy.HandleFailure(String message)
   at FluentAssertions.Execution.AssertionScope.FailWith(Func`1 failReasonFunc)
   at FluentAssertions.Execution.AssertionScope.FailWith(Func`1 failReasonFunc)
   at FluentAssertions.Execution.AssertionScope.FailWith(String message, Object[] args)
   at FluentAssertions.Collections.GenericCollectionAssertions`3.ContainInOrder(IEnumerable`1 expected, String because, Object[] becauseArgs)
   at FluentAssertions.Collections.GenericCollectionAssertions`3.ContainInOrder(T[] expected)
   at ConsoleApp38.Program.Main(String[] args) in S:\t\ConsoleApp38\ConsoleApp38\Program.cs:line 39

Expected behavior:

I would expect the assertion to save that data it saw when checking for success or failure, and re-use that for the message formatter.

Actual behavior:

What I described above. Assertion fails on older data, and prints newer data which are correct and leave me stumped about what is wrong.

Versions

  • Which version of Fluent Assertions are you using? 6.7.0
  • Which .NET runtime and version are you targeting? E.g. .NET framework 4.6.1 or .NET Core 2.1. Can repro on both net6 and net48.

Additional Information

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
jnyrupcommented, Sep 22, 2022
1reaction
jnyrupcommented, Sep 19, 2022

I don’t imagine we want to guard against concurrency.

In terms of efficiency I have an idea about wrapping non-materialized enumerables into a lazily materializing collection. One of the cases to take into account is that we e.g. don’t want to enumerate an infinite sequence to tell if it is empty or not.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How do I Unit Test a method that returns IEnumerable
Try: ClassToTest c = new ClassToTest(); var expected = int[] { 1, 2, 3, 4, 5 }; var actual = c.Numbers().ToArray(); CollectionAssert.
Read more >
Java static code analysis: AssertJ assertions with " ...
This rule raises an issue when a Consumer argument of any of the above methods does not contain any assertion. Noncompliant code example....
Read more >
Assertions comparing incompatible types should not be ...
Assertions comparing incompatible types always fail, and negative assertions always pass. ... "indexOf" checks should not be for positive numbers.
Read more >
Check that an Enumerable Contains the Same Items
No, they are not?! Assert.That(collection, Is.EqualTo(array).ByReference) should compare the items in the enumerable using reference equality. Assert.That( ...
Read more >
Extensibility - Fluent Assertions
A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found