Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/TestFramework/TestFramework/Assertions/Assert.Contains.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,49 @@ public static T ContainsSingle<T>(IEnumerable<T> collection, string? message = "
return default;
}

/// <summary>
/// Tests whether the specified collection contains exactly one element.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="message">The message to display when the assertion fails.</param>
/// <param name="collectionExpression">
/// The syntactic expression of collection as given by the compiler via caller argument expression.
/// Users shouldn't pass a value for this parameter.
/// </param>
/// <returns>The item.</returns>
public static object ContainsSingle(IEnumerable collection, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
{
int actualCount = 0;
object? firstItem = null;

foreach (object? item in collection)
{
if (actualCount == 0)
{
firstItem = item;
}

actualCount++;

// Early exit if we already know there's more than one item
if (actualCount > 1)
{
break;
}
}

if (actualCount == 1)
{
return firstItem!;
}

string userMessage = BuildUserMessageForCollectionExpression(message, collectionExpression);
ThrowAssertContainsSingleFailed(actualCount, userMessage);

// Unreachable code but compiler cannot work it out
return default!;
}

/// <summary>
/// Tests whether the specified collection contains exactly one element that matches the given predicate.
/// </summary>
Expand Down Expand Up @@ -168,6 +211,52 @@ public static T ContainsSingle<T>(Func<T, bool> predicate, IEnumerable<T> collec
return default;
}

/// <summary>
/// Tests whether the specified collection contains exactly one element that matches the given predicate.
/// </summary>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="collection">The collection.</param>
/// <param name="message">The message to display when the assertion fails.</param>
/// <param name="predicateExpression">
/// The syntactic expression of predicate as given by the compiler via caller argument expression.
/// Users shouldn't pass a value for this parameter.
/// </param>
/// <param name="collectionExpression">
/// The syntactic expression of collection as given by the compiler via caller argument expression.
/// Users shouldn't pass a value for this parameter.
/// </param>
/// <returns>The item that matches the predicate.</returns>
public static object ContainsSingle(Func<object, bool> predicate, IEnumerable collection, string? message = "", [CallerArgumentExpression(nameof(predicate))] string predicateExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
{
var matchingElements = new List<object>();

foreach (object? item in collection)
{
if (predicate(item))
{
matchingElements.Add(item);

// Early exit optimization - no need to continue if we already have more than one match
if (matchingElements.Count > 1)
{
break;
}
}
}

int actualCount = matchingElements.Count;
if (actualCount == 1)
{
return matchingElements[0];
}

string userMessage = BuildUserMessageForPredicateExpressionAndCollectionExpression(message, predicateExpression, collectionExpression);
ThrowAssertSingleMatchFailed(actualCount, userMessage);

// Unreachable code but compiler cannot work it out
return default!;
}

#endregion // ContainsSingle

#region Contains
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ContainsSingle(System.Collections.IEnumerable! collection, string? message = "", string! collectionExpression = "") -> object!
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ContainsSingle(System.Func<object!, bool>! predicate, System.Collections.IEnumerable! collection, string? message = "", string! predicateExpression = "", string! collectionExpression = "") -> object!
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,21 @@ public void ContainsSingle_NoMessage_WithSingleElement_ReturnsElement()
result.Should().Be(100);
}

/// <summary>
/// Tests the ContainsSingle method without message parameters where the collection has a single element.
/// </summary>
public void ContainsSingle_InNonGenericCollection_NoMessage_WithSingleElement_ReturnsElement()
{
// Arrange
var collection = new ArrayList { 100 };

// Act
object? result = Assert.ContainsSingle(collection);

// Assert
result.Should().Be(100);
}

/// <summary>
/// Tests the ContainsSingle method with a message where the collection has a single element.
/// </summary>
Expand All @@ -294,6 +309,21 @@ public void ContainsSingle_WithMessage_WithSingleElement_ReturnsElement()
result.Should().Be("OnlyOne");
}

/// <summary>
/// Tests the ContainsSingle method with a message where the collection has a single element.
/// </summary>
public void ContainsSingle_InNonGenericCollection_WithMessage_WithSingleElement_ReturnsElement()
{
// Arrange
var collection = new ArrayList { "OnlyOne" };

// Act
object? result = Assert.ContainsSingle(collection, "Custom message");

// Assert
result.Should().Be("OnlyOne");
}

/// <summary>
/// Tests the ContainsSingle method that uses an interpolated string handler when the collection has multiple elements.
/// Expects an exception.
Expand All @@ -311,6 +341,35 @@ public void ContainsSingle_InterpolatedHandler_WithMultipleElements_ThrowsExcept
action.Should().Throw<AssertFailedException>().WithMessage("Assert.ContainsSingle failed. Expected collection to contain exactly one element but found 3 element(s). 'collection' expression: 'collection'. ");
}

/// <summary>
/// Tests the ContainsSingle method without message parameters where the collection has a single element.
/// </summary>
public void ContainsSingle_InNonGenericCollection_NoMessage_WithNull_ReturnsElement()
{
// Arrange
var collection = new ArrayList { null };

// Act
object? result = Assert.ContainsSingle(collection);

// Assert
result.Should().Be(null);
}

/// <summary>
/// Tests the ContainsSingle method without message parameters where the collection has a single element.
/// </summary>
public void ContainsSingle_InNonGenericCollection_NoMessage_WithEmptyCollection_ReturnsNoElement()
{
// Arrange
var collection = new ArrayList();

// Act
Action action = () => Assert.ContainsSingle(collection);

// Assert
action.Should().Throw<AssertFailedException>();
}
#endregion

#region Contains Tests
Expand Down