diff --git a/Directory.Packages.props b/Directory.Packages.props
index 7f98568f8e4..016cbb603a8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -41,6 +41,7 @@
+
diff --git a/src/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs b/src/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs
index 7db6053a6c0..6bdb8917c61 100644
--- a/src/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs
+++ b/src/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs
@@ -731,7 +731,7 @@ private static MockMemberModel CreateMethodModel(IMethodSymbol method, ref int m
Constraints = tp.GetGenericConstraints(),
HasReferenceTypeConstraint = tp.HasReferenceTypeConstraint,
HasValueTypeConstraint = tp.HasValueTypeConstraint,
- HasAnnotatedNullableUsage = tp.IsUnconstrainedWithNullableUsage(method)
+ HasAnnotatedNullableUsage = tp.NeedsDefaultConstraintForNullableUsage(method)
}).ToImmutableArray()
),
ExplicitInterfaceName = explicitInterfaceName,
diff --git a/src/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs b/src/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs
index 3b39d756508..1ef284ba654 100644
--- a/src/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs
+++ b/src/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs
@@ -54,9 +54,13 @@ public static string GetGenericConstraints(this ITypeParameterSymbol typeParam)
return string.Join(", ", constraints);
}
- public static bool IsUnconstrainedWithNullableUsage(this ITypeParameterSymbol typeParam, IMethodSymbol method)
+ public static bool NeedsDefaultConstraintForNullableUsage(this ITypeParameterSymbol typeParam, IMethodSymbol method)
{
- if (!typeParam.IsUnconstrained())
+ // On overrides and explicit interface implementations, T? is parsed as Nullable
+ // unless T is known to be a reference/value type or a 'where T : default' clause is
+ // present. Interface-only constraints (e.g. Kiota's 'where T : IParsable') leave T
+ // unknown, so they need 'default' too — not just fully unconstrained parameters (#6456).
+ if (typeParam.IsReferenceType || typeParam.IsValueType)
{
return false;
}
diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/KiotaMockTests.cs b/tests/TUnit.Mocks.SourceGenerator.Tests/KiotaMockTests.cs
new file mode 100644
index 00000000000..7ac7abb5899
--- /dev/null
+++ b/tests/TUnit.Mocks.SourceGenerator.Tests/KiotaMockTests.cs
@@ -0,0 +1,62 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.Kiota.Abstractions;
+
+namespace TUnit.Mocks.SourceGenerator.Tests;
+
+///
+/// Regression tests for #6456: mocking Microsoft.Kiota.Abstractions.IRequestAdapter, whose
+/// generic methods carry an interface constraint (where ModelType : IParsable). Explicit
+/// interface implementations inherit constraints, but T? in them is parsed as Nullable<T>
+/// unless a 'where T : default' clause is emitted — which is required whenever T is not known
+/// to be a reference or value type, not just when T is fully unconstrained.
+///
+public class KiotaMockTests : SnapshotTestBase
+{
+ private const string Source = """
+ using Microsoft.Kiota.Abstractions;
+ using TUnit.Mocks;
+
+ [assembly: GenerateMock(typeof(IRequestAdapter))]
+ """;
+
+ private static IEnumerable KiotaReferences() =>
+ [MetadataReference.CreateFromFile(typeof(IRequestAdapter).Assembly.Location)];
+
+ [Test]
+ public void KiotaIRequestAdapter_ConstrainedGenericMethod_EmitsDefaultConstraint()
+ {
+ var output = string.Join("\n", RunGenerator(Source, KiotaReferences()));
+
+ // SendAsync has 'where ModelType : IParsable' and returns Task;
+ // its explicit implementation must carry 'where ModelType : default'.
+ AssertContains(output,
+ "global::Microsoft.Kiota.Abstractions.IRequestAdapter.SendAsync");
+ AssertContains(output, "where ModelType : default");
+ }
+
+ [Test]
+ public void KiotaIRequestAdapter_GeneratedMock_HasNoConstraintErrors()
+ {
+ var errors = GetGeneratedCompilationErrors(Source, KiotaReferences());
+
+ // The exact errors reported in #6456. Other diagnostics (e.g. CS1520/CS1513 from C# 14
+ // extension() blocks that the test-pinned Roslyn cannot parse) are infra limitations
+ // and intentionally not asserted here — see SnapshotTestBase.
+ foreach (var id in (string[])["CS0539", "CS0535", "CS0453", "CS0314"])
+ {
+ var match = errors.FirstOrDefault(e => string.Equals(e.Id, id, StringComparison.Ordinal));
+ if (match is not null)
+ {
+ throw new InvalidOperationException($"Generated code produced {id}: {match}");
+ }
+ }
+ }
+
+ private static void AssertContains(string text, string expected)
+ {
+ if (!text.Contains(expected, StringComparison.Ordinal))
+ {
+ throw new InvalidOperationException($"Expected generated output to contain: {expected}");
+ }
+ }
+}
diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj b/tests/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj
index 281d5117d2b..affa8e16d20 100644
--- a/tests/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj
+++ b/tests/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj
@@ -9,6 +9,7 @@
+
diff --git a/tests/TUnit.Mocks.Tests/Issue6456Tests.cs b/tests/TUnit.Mocks.Tests/Issue6456Tests.cs
new file mode 100644
index 00000000000..c34f8d039d8
--- /dev/null
+++ b/tests/TUnit.Mocks.Tests/Issue6456Tests.cs
@@ -0,0 +1,51 @@
+using Microsoft.Kiota.Abstractions;
+using Microsoft.Kiota.Abstractions.Serialization;
+using TUnit.Mocks;
+
+namespace TUnit.Mocks.Tests;
+
+///
+/// Regression tests for #6456: Microsoft.Kiota.Abstractions.IRequestAdapter could not be mocked
+/// because its generic methods carry 'where ModelType : IParsable'. The generated explicit
+/// interface implementations omitted the 'where ModelType : default' clause, so the compiler
+/// parsed Task<ModelType?> as Task<Nullable<ModelType>> (CS0453/CS0539/CS0535/CS0314).
+///
+public class Issue6456Tests
+{
+ private sealed class TestModel : IParsable
+ {
+ public IDictionary> GetFieldDeserializers() =>
+ new Dictionary>();
+
+ public void Serialize(ISerializationWriter writer)
+ {
+ }
+ }
+
+ [Test]
+ public async Task SendNoContentAsync_Can_Be_Mocked_And_Verified()
+ {
+ var mock = IRequestAdapter.Mock();
+ var requestInfo = new RequestInformation();
+
+ await mock.Object.SendNoContentAsync(requestInfo);
+
+ mock.SendNoContentAsync(Any(), Any(), Any()).WasCalled(Times.Once);
+ }
+
+ [Test]
+ public async Task SendAsync_With_Constrained_Generic_Returns_Configured_Value()
+ {
+ var mock = IRequestAdapter.Mock();
+ var expected = new TestModel();
+ mock.SendAsync(Any(), Any(), Any(), Any()).Returns(expected);
+
+ var result = await mock.Object.SendAsync(
+ new RequestInformation(),
+ static _ => new TestModel(),
+ errorMapping: null,
+ CancellationToken.None);
+
+ await Assert.That(result).IsSameReferenceAs(expected);
+ }
+}
diff --git a/tests/TUnit.Mocks.Tests/TUnit.Mocks.Tests.csproj b/tests/TUnit.Mocks.Tests/TUnit.Mocks.Tests.csproj
index edac9eae952..77a851d7b3b 100644
--- a/tests/TUnit.Mocks.Tests/TUnit.Mocks.Tests.csproj
+++ b/tests/TUnit.Mocks.Tests/TUnit.Mocks.Tests.csproj
@@ -16,6 +16,7 @@
+