Skip to content

Conversation

AtolagbeMuiz
Copy link
Contributor

This Pull Request fixes Collection assert methods should accept IEnumerable collections #6184

This implementation involves an overload of Assert.Contains which allows non-generic IEnumerable collection like ArrayList and Stack.

Current Behaviour
Currently, the Assert.Contains function doesn't allow non-generic collection like ArrayList as this throws compile time error as seen below

image

Expected Behaviour
With this Implementation, Assert.Contains will allow non-generic collection as seen below;

image image

/// </summary>
/// <param name="expected">The expected item.</param>
/// <param name="collection">The non-generic collection (like ArrayList).</param>
public static void Contains(object expected, IEnumerable collection)
Copy link
Member

Choose a reason for hiding this comment

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

I think we need similar non-generic overloads also for IsEmpty and HasCount. Then MSTEST0037 analyzer need to be adjusted as well to account for the existence of the non-generic overloads:

private static bool IsBCLCollectionType(ITypeSymbol type, INamedTypeSymbol objectTypeSymbol)
// Check if the type implements IEnumerable<T> (but is not string)
// Note: Assert.Contains/IsEmpty/HasCount for collections accept IEnumerable<T>, but not IEnumerable.
=> type.SpecialType != SpecialType.System_String && type.AllInterfaces.Any(i =>
i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T) &&
// object is coming from BCL and it's expected to always have a public key.
type.ContainingAssembly.Identity.HasPublicKey == objectTypeSymbol.ContainingAssembly.Identity.HasPublicKey &&
type.ContainingAssembly.Identity.PublicKey.SequenceEqual(objectTypeSymbol.ContainingAssembly.Identity.PublicKey);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I intend to implement this for IsEmpty etc. as well.. still yet to have a good understanding of the Analyzer part.

Copy link
Member

@Youssef1313 Youssef1313 Aug 22, 2025

Choose a reason for hiding this comment

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

Once you add the overload for HasCount, this analyzer test will fail:

public async Task WhenAssertAreEqualWithCollectionCountUsingNonGenericCollection()

The test will need to be updated as following (note: I'm writing it directly on GitHub so there might be small mistakes):

    [TestMethod]
    public async Task WhenAssertAreEqualWithCollectionCountUsingNonGenericCollection()
    {
        string code = """
            using System;
            using System.Collections;
            using System.Collections.Generic;
            using Microsoft.VisualStudio.TestTools.UnitTesting;

            [TestClass]
            public class MyTestClass
            {
                [TestMethod]
                public void MyTestMethod()
                {
                    var x = new Hashtable();
                    {|#0:Assert.AreEqual(4, x.Count)|};
                }
            }
            """;

        string fixedCode = """
            using System;
            using System.Collections;
            using System.Collections.Generic;
            using Microsoft.VisualStudio.TestTools.UnitTesting;

            [TestClass]
            public class MyTestClass
            {
                [TestMethod]
                public void MyTestMethod()
                {
                    var x = new Hashtable();
                    Assert.HasCount(4, x);
                }
            }
            """;

        await VerifyCS.VerifyCodeFixAsync(
            code,
            // /0/Test0.cs(13,9): info MSTEST0037: Use 'Assert.HasCount' instead of 'Assert.AreEqual'
            VerifyCS.DiagnosticIgnoringAdditionalLocations().WithLocation(0).WithArguments("HasCount", "AreEqual"),
            fixedCode);
    }

The analyzer implementation then will need to check System_Collections_Generic_IEnumerable instead of System_Collections_Generic_IEnumerable_T

Note: because we use AllInterfaces, and IEnumerable<T> is IEnumerable, we need only IEnumerable check. We don't need both IEnumerable<T> and IEnumerable.
Note 2: OriginalDefinition access will become redundant, because we are no longer dealing with generics.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i will revert back to this once I want to implement for HasCount

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @AtolagbeMuiz.

Are you planning to continue that soon? It's best to have all the overloads in the same PR as the analyzer is likely handling all these methods in the same way (even if we have only test coverage for HasCount)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @Youssef1313, should the implementation for other Assert methods like HasCount be in this same PR and branch and not as a different branch??

was intending to work on it on a different branch.. I should commence work on it this week.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I prefer if it's the same PR in this case. Thanks @AtolagbeMuiz!

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.

Collection assert methods should accept IEnumerable collections
3 participants