diff --git a/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs b/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs
index 16f67d46..36a4f1e6 100644
--- a/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs
+++ b/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs
@@ -33,11 +33,24 @@ public GenericParameter(ITypeParameterSymbol typeSymbol)
public bool IsClass { get; }
#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high
- public void AppendWhereConstraint(StringBuilder sb, string prefix)
+ public void AppendWhereConstraint(StringBuilder sb, string prefix, bool isExplicitInterfaceImpl = false)
{
- if (!ConstraintTypes.Any() && !IsStruct && !IsClass && !IsNotNull && !IsUnmanaged && !HasConstructor &&
- !AllowsRefStruct)
+ bool isUnconstrained = !ConstraintTypes.Any() && !IsStruct && !IsClass && !IsNotNull && !IsUnmanaged &&
+ !HasConstructor && !AllowsRefStruct;
+
+ if (isUnconstrained)
+ {
+ if (isExplicitInterfaceImpl)
+ {
+ sb.AppendLine().Append(prefix).Append("where ").Append(Name).Append(" : default");
+ }
+
+ return;
+ }
+
+ if (isExplicitInterfaceImpl)
{
+ // Constrained type parameters are inherited in explicit interface implementations (CS0460)
return;
}
diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs
index 34a29cdb..d83af1d6 100644
--- a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs
+++ b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs
@@ -1034,6 +1034,7 @@ private static void AppendMockSubject_ImplementClass_AddEvent(StringBuilder sb,
.Append("? ").Append(backingFieldName).Append(';').AppendLine();
backingFieldAccess = $"this.{backingFieldName}";
}
+
sb.Append("\t\t/// ");
sb.Append(@event.Attributes, "\t\t");
@@ -1499,7 +1500,7 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb,
{
foreach (GenericParameter gp in method.GenericParameters.Value)
{
- gp.AppendWhereConstraint(sb, "\t\t\t");
+ gp.AppendWhereConstraint(sb, "\t\t\t", explicitInterfaceImplementation);
}
}
@@ -1797,7 +1798,7 @@ private static void DefineSetupInterface(StringBuilder sb, Class @class, MemberT
{
foreach (bool[] valueFlags in GenerateValueFlagCombinations(indexer.IndexerParameters.Value))
{
- AppendIndexerSetupDefinition(sb, indexer, valueFlags, hasOverloadResolutionPriority: hasOverloadResolutionPriority);
+ AppendIndexerSetupDefinition(sb, indexer, valueFlags, hasOverloadResolutionPriority);
}
}
else
@@ -1805,7 +1806,7 @@ private static void DefineSetupInterface(StringBuilder sb, Class @class, MemberT
bool[] allValueFlags = indexer.IndexerParameters.Value.Select(p => p.CanUseNullableParameterOverload()).ToArray();
if (allValueFlags.Any(f => f))
{
- AppendIndexerSetupDefinition(sb, indexer, allValueFlags, hasOverloadResolutionPriority: hasOverloadResolutionPriority);
+ AppendIndexerSetupDefinition(sb, indexer, allValueFlags, hasOverloadResolutionPriority);
}
}
}
@@ -2216,6 +2217,14 @@ private static void AppendMethodSetupImplementation(StringBuilder sb, Method met
sb.Append(")");
}
+ if (method.GenericParameters is not null && method.GenericParameters.Value.Count > 0)
+ {
+ foreach (GenericParameter gp in method.GenericParameters.Value)
+ {
+ gp.AppendWhereConstraint(sb, "\t\t\t", true);
+ }
+ }
+
sb.AppendLine();
sb.AppendLine("\t\t{");
if (method.ReturnType != Type.Void)
@@ -2630,7 +2639,7 @@ private static void DefineVerifyInterface(StringBuilder sb, Class @class, string
{
foreach (bool[] valueFlags in GenerateValueFlagCombinations(indexer.IndexerParameters.Value))
{
- AppendIndexerVerifyDefinition(sb, indexer, verifyName, valueFlags, hasOverloadResolutionPriority: hasOverloadResolutionPriority);
+ AppendIndexerVerifyDefinition(sb, indexer, verifyName, valueFlags, hasOverloadResolutionPriority);
}
}
else
@@ -2638,7 +2647,7 @@ private static void DefineVerifyInterface(StringBuilder sb, Class @class, string
bool[] allValueFlags = indexer.IndexerParameters.Value.Select(p => p.CanUseNullableParameterOverload()).ToArray();
if (allValueFlags.Any(f => f))
{
- AppendIndexerVerifyDefinition(sb, indexer, verifyName, allValueFlags, hasOverloadResolutionPriority: hasOverloadResolutionPriority);
+ AppendIndexerVerifyDefinition(sb, indexer, verifyName, allValueFlags, hasOverloadResolutionPriority);
}
}
}
@@ -2955,6 +2964,14 @@ private static void AppendMethodVerifyImplementation(StringBuilder sb, Method me
}
sb.Append(")");
+ if (method.GenericParameters is not null && method.GenericParameters.Value.Count > 0)
+ {
+ foreach (GenericParameter gp in method.GenericParameters.Value)
+ {
+ gp.AppendWhereConstraint(sb, "\t\t\t", true);
+ }
+ }
+
sb.AppendLine();
sb.Append("\t\t\t=> this.").Append(mockRegistryName).Append(".Method<").Append(verifyName).Append(">(this, ");
diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs
index bf71760d..84ce39f9 100644
--- a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs
+++ b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs
@@ -6,6 +6,43 @@ public sealed partial class ClassTests
{
public sealed class MethodTests
{
+ [Fact]
+ public async Task ExplicitInterfaceImplementation_WithUnconstrainedGeneric_ShouldHaveDefaultConstraint()
+ {
+ GeneratorResult result = Generator
+ .Run("""
+ using System.Threading.Tasks;
+ using Mockolate;
+
+ namespace MyCode;
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ _ = IMyService.CreateMock().Implementing();
+ }
+ }
+
+ public interface IMyService
+ {
+ void MyMethod();
+ }
+
+ public interface IMyOtherService
+ {
+ Task DoSomethingAsync();
+ }
+ """);
+
+ await That(result.Sources).ContainsKey("Mock.IMyService__IMyOtherService.g.cs").WhoseValue
+ .Contains("""
+ ///
+ global::System.Threading.Tasks.Task global::MyCode.IMyOtherService.DoSomethingAsync()
+ where T : default
+ {
+ """).IgnoringNewlineStyle();
+ }
+
[Theory]
[InlineData("class, T")]
[InlineData("struct")]
diff --git a/Tests/Mockolate.Tests/MockTests.cs b/Tests/Mockolate.Tests/MockTests.cs
index 5b0c0440..8a6ddc74 100644
--- a/Tests/Mockolate.Tests/MockTests.cs
+++ b/Tests/Mockolate.Tests/MockTests.cs
@@ -152,7 +152,7 @@ public async Task Create_WithRequiredParameters_WithEmptyParameters_ShouldThrowM
{
void Act()
{
- _ = MyBaseClassWithConstructor.CreateMock(constructorParameters: []);
+ _ = MyBaseClassWithConstructor.CreateMock([]);
}
await That(Act).Throws()
@@ -190,11 +190,11 @@ public async Task Create_WithSetups_ShouldAllowChangingTheSetupSubjectInCallback
public async Task Create_WithSetups_ShouldApplySetups()
{
IMyService mock = IMyService.CreateMock(setup =>
- {
- setup.Multiply(It.Is(1), It.IsAny()).Returns(2);
- setup.Multiply(It.Is(2), It.IsAny()).Returns(4);
- setup.Multiply(It.Is(3), It.IsAny()).Returns(8);
- });
+ {
+ setup.Multiply(It.Is(1), It.IsAny()).Returns(2);
+ setup.Multiply(It.Is(2), It.IsAny()).Returns(4);
+ setup.Multiply(It.Is(3), It.IsAny()).Returns(8);
+ });
int result1 = mock.Multiply(1, null);
int result2 = mock.Multiply(2, null);
@@ -242,6 +242,21 @@ public async Task GenericMethodWithWhereClause_ShouldWork()
await That(sut.Mock.Verify.MyMethod(It.IsTrue())).Once();
}
+ [Fact]
+ public async Task GenericMethodWithWhereClause_WhenImplementingAdditionalInterface_ShouldWork()
+ {
+ IChocolateDispenser sut = IChocolateDispenser.CreateMock()
+ .Implementing();
+ IMyServiceWithGenericMethodsWithWhereClause service = (IMyServiceWithGenericMethodsWithWhereClause)sut;
+
+ sut.Mock.As().Setup.MyMethod(It.IsTrue()).Returns(3);
+
+ int result = service.MyMethod(true);
+
+ await That(result).IsEqualTo(3);
+ await That(sut.Mock.As().Verify.MyMethod(It.IsTrue())).Once();
+ }
+
[Fact]
public async Task ToString_ShouldReturnImplementedType()
{
@@ -262,6 +277,17 @@ public async Task ToString_WithAdditionalImplementations_ShouldReturnImplemented
await That(result).IsEqualTo("Mockolate.Tests.TestHelpers.IChocolateDispenser mock that also implements Mockolate.Tests.MockTests.IMyService");
}
+ [Fact]
+ public async Task TypeWithMockRegistryMembers_ShouldUseUniqueName()
+ {
+ IServiceWithMockRegistryMembers sut = IServiceWithMockRegistryMembers.CreateMock();
+ sut.Mock.Setup.MockRegistry_1.Returns("foo");
+
+ string result = sut.MockRegistry_1;
+
+ await That(result).IsEqualTo("foo");
+ }
+
[Fact]
public async Task WhenTypeHasMockAndMockolate_MockProperty_ShouldAppendNumbers()
{
@@ -308,17 +334,6 @@ public async Task WithoutConstructorParameters_MockConstructorParametersShouldBe
await That(mock.MockRegistry.ConstructorParameters).IsNull();
}
- [Fact]
- public async Task TypeWithMockRegistryMembers_ShouldUseUniqueName()
- {
- IServiceWithMockRegistryMembers sut = IServiceWithMockRegistryMembers.CreateMock();
- sut.Mock.Setup.MockRegistry_1.Returns("foo");
-
- string result = sut.MockRegistry_1;
-
- await That(result).IsEqualTo("foo");
- }
-
public interface MyInterfaceWithMockProperty
{
int Mock { get; }
@@ -403,10 +418,10 @@ public interface IMyServiceWithGenericMethodsWithWhereClause
public interface IServiceWithMockRegistryMembers
{
- event EventHandler MockRegistry;
- int MockolateMockRegistry(bool value);
// ReSharper disable once InconsistentNaming
string MockRegistry_1 { get; }
+ event EventHandler MockRegistry;
+ int MockolateMockRegistry(bool value);
}
public sealed class Nested