Skip to content
Merged
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
120 changes: 120 additions & 0 deletions TUnit.Assertions.Tests/IgnoringTypeEquivalentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,124 @@ private class MyClassWithoutDates
public string Name { get; set; } = string.Empty;
public int Value { get; set; }
}

// Test classes for ValueType/Tuple tests
private class IgnoreMe
{
public string Message { get; set; } = string.Empty;

public IgnoreMe() { }
public IgnoreMe(string message) => Message = message;
}

private class ClassWithTupleProperty
{
public string Name { get; set; } = string.Empty;
public (IgnoreMe, IgnoreMe) Ignores { get; set; }
public int Value { get; set; }
}

private class ClassWithNestedTupleProperty
{
public string Name { get; set; } = string.Empty;
public ((IgnoreMe, int), string) NestedIgnores { get; set; }
public int Value { get; set; }
}

private class ClassWithMixedTupleProperty
{
public string Name { get; set; } = string.Empty;
public (IgnoreMe, int) MixedTuple { get; set; }
public int Value { get; set; }
}

[Test]
public async Task IgnoringType_In_Tuple_Properties_Are_Ignored()
{
var object1 = new ClassWithTupleProperty
{
Name = "Test",
Ignores = (new IgnoreMe("foobar"), new IgnoreMe("foobar")),
Value = 123
};

var object2 = new ClassWithTupleProperty
{
Name = "Test",
Ignores = (new IgnoreMe("baz"), new IgnoreMe("baz")),
Value = 123
};

await TUnitAssert.That(object1)
.IsEquivalentTo(object2)
.IgnoringType<IgnoreMe>();
}

[Test]
public async Task IgnoringType_In_Nested_Tuple_Properties_Are_Ignored()
{
var object1 = new ClassWithNestedTupleProperty
{
Name = "Test",
NestedIgnores = ((new IgnoreMe("foobar"), 1), "hello"),
Value = 123
};

var object2 = new ClassWithNestedTupleProperty
{
Name = "Test",
NestedIgnores = ((new IgnoreMe("baz"), 1), "hello"),
Value = 123
};

await TUnitAssert.That(object1)
.IsEquivalentTo(object2)
.IgnoringType<IgnoreMe>();
}

[Test]
public async Task IgnoringType_In_Mixed_Tuple_Still_Compares_Non_Ignored_Parts()
{
var object1 = new ClassWithMixedTupleProperty
{
Name = "Test",
MixedTuple = (new IgnoreMe("foobar"), 42),
Value = 123
};

var object2 = new ClassWithMixedTupleProperty
{
Name = "Test",
MixedTuple = (new IgnoreMe("baz"), 99), // Different int value
Value = 123
};

// Should fail because the int part (42 vs 99) is different
await TUnitAssert.That(object1)
.IsNotEquivalentTo(object2)
.IgnoringType<IgnoreMe>();
}

[Test]
public async Task IgnoringType_In_Mixed_Tuple_Passes_When_NonIgnored_Parts_Match()
{
var object1 = new ClassWithMixedTupleProperty
{
Name = "Test",
MixedTuple = (new IgnoreMe("foobar"), 42),
Value = 123
};

var object2 = new ClassWithMixedTupleProperty
{
Name = "Test",
MixedTuple = (new IgnoreMe("baz"), 42), // Same int value
Value = 123
};

// Should pass because the int part is the same and IgnoreMe is ignored
await TUnitAssert.That(object1)
.IsEquivalentTo(object2)
.IgnoringType<IgnoreMe>();
}
}
31 changes: 30 additions & 1 deletion TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ internal AssertionResult CompareObjects(
var expectedType = expected.GetType();

// Handle primitive types and strings
if (TypeHelper.IsPrimitiveOrWellKnownType(actualType))
// But don't treat generic value types as primitive if they contain ignored types
if (TypeHelper.IsPrimitiveOrWellKnownType(actualType) && !ContainsIgnoredGenericArgument(actualType))
{
if (!Equals(actual, expected))
{
Expand Down Expand Up @@ -304,6 +305,34 @@ private bool ShouldIgnoreType(Type type)
return false;
}

/// <summary>
/// Checks if a generic type (like ValueTuple) contains any ignored types as generic arguments.
/// This prevents tuples containing ignored types from being compared as primitive values.
/// </summary>
private bool ContainsIgnoredGenericArgument(Type type)
{
if (!type.IsGenericType || _ignoredTypes.Count == 0)
{
return false;
}

foreach (var genericArg in type.GetGenericArguments())
{
if (_ignoredTypes.Contains(genericArg))
{
return true;
}

// Recursively check nested generic types (e.g., Tuple<Tuple<IgnoreMe, int>, string>)
if (ContainsIgnoredGenericArgument(genericArg))
{
return true;
}
}

return false;
}

private static string FormatValue(object? value)
{
if (value == null)
Expand Down
Loading