diff --git a/TUnit.Assertions.Tests/Bugs/IsEquivalentTo_TypeProperty_Tests.cs b/TUnit.Assertions.Tests/Bugs/IsEquivalentTo_TypeProperty_Tests.cs new file mode 100644 index 0000000000..5f8db3f298 --- /dev/null +++ b/TUnit.Assertions.Tests/Bugs/IsEquivalentTo_TypeProperty_Tests.cs @@ -0,0 +1,57 @@ +namespace TUnit.Assertions.Tests.Bugs; + +/// +/// Tests for IsEquivalentTo with records containing Type properties. +/// Type objects have properties (like DeclaringMethod) that throw InvalidOperationException +/// when accessed on non-generic-parameter types, so Type must be treated as a well-known +/// primitive type and compared by equality rather than structural decomposition. +/// +public class IsEquivalentTo_TypeProperty_Tests +{ + public record RecordWithType(Type Type); + + public record RecordWithMultipleTypes(Type First, Type Second, string Name); + + [Test] + public async Task IsEquivalentTo_RecordWithTypeProperty_SameType_ShouldSucceed() + { + var r = new RecordWithType(typeof(string)); + await Assert.That(r).IsEquivalentTo(r); + } + + [Test] + public async Task IsEquivalentTo_RecordWithTypeProperty_EqualTypes_ShouldSucceed() + { + var r1 = new RecordWithType(typeof(string)); + var r2 = new RecordWithType(typeof(string)); + await Assert.That(r1).IsEquivalentTo(r2); + } + + [Test] + public async Task IsEquivalentTo_RecordWithTypeProperty_DifferentTypes_ShouldFail() + { + var r1 = new RecordWithType(typeof(string)); + var r2 = new RecordWithType(typeof(int)); + + var exception = await Assert.ThrowsAsync( + async () => await Assert.That(r1).IsEquivalentTo(r2)); + + await Assert.That(exception).IsNotNull(); + } + + [Test] + public async Task IsEquivalentTo_RecordWithMultipleTypeProperties_ShouldSucceed() + { + var r1 = new RecordWithMultipleTypes(typeof(string), typeof(int), "test"); + var r2 = new RecordWithMultipleTypes(typeof(string), typeof(int), "test"); + await Assert.That(r1).IsEquivalentTo(r2); + } + + [Test] + public async Task IsEquivalentTo_TypeDirectly_ShouldSucceed() + { + Type t1 = typeof(string); + Type t2 = typeof(string); + await Assert.That(t1).IsEquivalentTo(t2); + } +} diff --git a/TUnit.Assertions/Conditions/Helpers/TypeHelper.cs b/TUnit.Assertions/Conditions/Helpers/TypeHelper.cs index cec67d319d..81e080ca62 100644 --- a/TUnit.Assertions/Conditions/Helpers/TypeHelper.cs +++ b/TUnit.Assertions/Conditions/Helpers/TypeHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; +using System.Reflection; namespace TUnit.Assertions.Conditions.Helpers; @@ -78,6 +79,8 @@ public static bool IsPrimitiveOrWellKnownType(Type type) || type == typeof(DateTimeOffset) || type == typeof(TimeSpan) || type == typeof(Guid) + || typeof(Type).IsAssignableFrom(type) + || typeof(MemberInfo).IsAssignableFrom(type) #if NET6_0_OR_GREATER || type == typeof(DateOnly) || type == typeof(TimeOnly)