diff --git a/TUnit.Mocks.Analyzers.Tests/SealedClassMockAnalyzerTests.cs b/TUnit.Mocks.Analyzers.Tests/SealedClassMockAnalyzerTests.cs index 67591ec54a..7baa5c206a 100644 --- a/TUnit.Mocks.Analyzers.Tests/SealedClassMockAnalyzerTests.cs +++ b/TUnit.Mocks.Analyzers.Tests/SealedClassMockAnalyzerTests.cs @@ -13,8 +13,8 @@ public static class Mock { public static object Of() => default!; public static object Of(int behavior) => default!; - public static object OfPartial(params object[] args) => default!; - public static object OfPartial(int behavior, params object[] args) => default!; + public static object Of(int behavior, params object[] args) => default!; + public static object Of(params object[] args) => default!; } } """; @@ -141,7 +141,7 @@ public void Test() } [Test] - public async Task Sealed_Class_Via_OfPartial_Reports_TM001() + public async Task Sealed_Class_With_Constructor_Args_Reports_TM001() { await Verifier.VerifyAnalyzerAsync( MockStub + """ @@ -152,7 +152,7 @@ public class TestClass { public void Test() { - {|#0:TUnit.Mocks.Mock.OfPartial()|}; + {|#0:TUnit.Mocks.Mock.Of(0, "arg")|}; } } """, diff --git a/TUnit.Mocks.Analyzers.Tests/StructMockAnalyzerTests.cs b/TUnit.Mocks.Analyzers.Tests/StructMockAnalyzerTests.cs index 15773b674f..b3a293737d 100644 --- a/TUnit.Mocks.Analyzers.Tests/StructMockAnalyzerTests.cs +++ b/TUnit.Mocks.Analyzers.Tests/StructMockAnalyzerTests.cs @@ -13,8 +13,8 @@ public static class Mock { public static object Of() => default!; public static object Of(int behavior) => default!; - public static object OfPartial(params object[] args) => default!; - public static object OfPartial(int behavior, params object[] args) => default!; + public static object Of(int behavior, params object[] args) => default!; + public static object Of(params object[] args) => default!; } } """; @@ -144,7 +144,7 @@ public void Test() } [Test] - public async Task Struct_Via_OfPartial_Reports_TM002() + public async Task Struct_With_Constructor_Args_Reports_TM002() { await Verifier.VerifyAnalyzerAsync( MockStub + """ @@ -155,7 +155,7 @@ public class TestClass { public void Test() { - {|#0:TUnit.Mocks.Mock.OfPartial()|}; + {|#0:TUnit.Mocks.Mock.Of(0, "arg")|}; } } """, diff --git a/TUnit.Mocks.Analyzers/SealedClassMockAnalyzer.cs b/TUnit.Mocks.Analyzers/SealedClassMockAnalyzer.cs index 51ca547a7f..5634f971cc 100644 --- a/TUnit.Mocks.Analyzers/SealedClassMockAnalyzer.cs +++ b/TUnit.Mocks.Analyzers/SealedClassMockAnalyzer.cs @@ -60,7 +60,7 @@ private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) private static bool IsMockOfMethod(IMethodSymbol method) { - return method.Name is "Of" or "OfPartial" + return method.Name is "Of" && method.ContainingType is { Name: "Mock" or "MockRepository", ContainingNamespace: { Name: "Mocks", ContainingNamespace: { Name: "TUnit", ContainingNamespace.IsGlobalNamespace: true } } } && method.IsGenericMethod; } diff --git a/TUnit.Mocks.Analyzers/StructMockAnalyzer.cs b/TUnit.Mocks.Analyzers/StructMockAnalyzer.cs index ba23b8754e..8361102ec4 100644 --- a/TUnit.Mocks.Analyzers/StructMockAnalyzer.cs +++ b/TUnit.Mocks.Analyzers/StructMockAnalyzer.cs @@ -60,7 +60,7 @@ private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) private static bool IsMockOfMethod(IMethodSymbol method) { - return method.Name is "Of" or "OfPartial" + return method.Name is "Of" && method.ContainingType is { Name: "Mock" or "MockRepository", ContainingNamespace: { Name: "Mocks", ContainingNamespace: { Name: "TUnit", ContainingNamespace.IsGlobalNamespace: true } } } && method.IsGenericMethod; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs index 13adafa40c..8504d3cc10 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs +++ b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs @@ -465,7 +465,7 @@ public class TestUsage { void M() { - var mock = Mock.OfPartial(); + var mock = Mock.Of(); } } """; @@ -505,7 +505,7 @@ public class TestUsage { void M() { - var mock = Mock.OfPartial(); + var mock = Mock.Of(); } } """; @@ -543,7 +543,7 @@ public class TestUsage { void M() { - var mock = Mock.OfPartial(); + var mock = Mock.Of(); } } """; @@ -610,4 +610,22 @@ void M() return VerifyGeneratorOutput(source); } + + [Test] + public Task GenerateMock_Attribute_With_Concrete_Class() + { + var source = """ + using TUnit.Mocks; + + [assembly: GenerateMock(typeof(MyService))] + + public class MyService + { + public virtual string GetValue() => "real"; + public virtual void DoWork() { } + } + """; + + return VerifyGeneratorOutput(source); + } } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/GenerateMock_Attribute_With_Concrete_Class.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/GenerateMock_Attribute_With_Concrete_Class.verified.txt new file mode 100644 index 0000000000..7e2aa30a4a --- /dev/null +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/GenerateMock_Attribute_With_Concrete_Class.verified.txt @@ -0,0 +1,101 @@ +// +#nullable enable + +namespace TUnit.Mocks.Generated +{ + internal static class MyService_PartialMockFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.Mock.RegisterFactory(Create); + } + + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new MyService_MockImpl(engine); + engine.Raisable = impl; + var mock = new global::TUnit.Mocks.Mock(impl, engine); + return mock; + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#nullable enable + +namespace TUnit.Mocks.Generated +{ + internal sealed class MyService_MockImpl : global::MyService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject + { + private readonly global::TUnit.Mocks.MockEngine _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal MyService_MockImpl(global::TUnit.Mocks.MockEngine engine) : base() + { + _engine = engine; + } + + public override string GetValue() + { + if (_engine.TryHandleCallWithReturn(0, "GetValue", global::System.Array.Empty(), "", out var __result)) + { + return __result; + } + return base.GetValue(); + } + + public override void DoWork() + { + if (_engine.TryHandleCall(1, "DoWork", global::System.Array.Empty())) + { + return; + } + base.DoWork(); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class MyService_MockMemberExtensions + { + public static global::TUnit.Mocks.MockMethodCall GetValue(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new global::TUnit.Mocks.MockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "GetValue", matchers); + } + + public static global::TUnit.Mocks.VoidMockMethodCall DoWork(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "DoWork", matchers); + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt index f28d3e5304..c3c0cd5fe2 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IReadWriter' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IReadWriter_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt index 3f08ae5dc2..3fbb33bd3b 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IAsyncService' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IAsyncService_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt index 46150bc827..bf271369ae 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt @@ -42,8 +42,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::INotifier' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new INotifier_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt index 34307f8987..eaad8de245 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IRepository' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IRepository_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt index 60df228ce1..3b379b0c3f 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt @@ -44,8 +44,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::TUnit.Mocks.Generated.IMyService_Mockable' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IMyService_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt index 5a29ef3950..252bf30236 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::ITest' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new ITest_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt index 02d88599a6..cdfbee5c1f 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt @@ -42,8 +42,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IService' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IService_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt index cc500a2dcf..e038c061a1 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IFoo' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IFoo_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt index e0e5a758cf..b3767dabd4 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IDictionary' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IDictionary_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt index 7198477d00..7716bf0dc4 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IFormatter' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IFormatter_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt index 1408e0e6d0..1810cf4132 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IRepository' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IRepository_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt index 47ff789005..ef113b37a0 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IBufferProcessor' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IBufferProcessor_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt index 4f298aeab3..98753fb6f3 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt @@ -60,8 +60,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::TUnit.Mocks.Generated.IServiceFactory_Mockable' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IServiceFactory_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt index 2545746aa2..b66a48bf84 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IMyService' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IMyService_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt index f7429cf72c..58122d02a5 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::ICalculator' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new ICalculator_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt index 248fbf53d1..bf0e926885 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt @@ -8,7 +8,7 @@ namespace TUnit.Mocks.Generated [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void Register() { - global::TUnit.Mocks.Mock.RegisterPartialFactory(Create); + global::TUnit.Mocks.Mock.RegisterFactory(Create); } private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt index 95e8bd4de9..90035b2400 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt @@ -8,7 +8,7 @@ namespace TUnit.Mocks.Generated [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void Register() { - global::TUnit.Mocks.Mock.RegisterPartialFactory(Create); + global::TUnit.Mocks.Mock.RegisterFactory(Create); } private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt index 8b93cc08ae..489fdb0270 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt @@ -8,7 +8,7 @@ namespace TUnit.Mocks.Generated [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void Register() { - global::TUnit.Mocks.Mock.RegisterPartialFactory(Create); + global::TUnit.Mocks.Mock.RegisterFactory(Create); } private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt index dbb7feef84..f3654e5425 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -11,8 +11,9 @@ namespace TUnit.Mocks.Generated global::TUnit.Mocks.Mock.RegisterFactory(Create); } - private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IGreeter' does not support constructor arguments, but {constructorArgs.Length} were provided."); var engine = new global::TUnit.Mocks.MockEngine(behavior); var impl = new IGreeter_MockImpl(engine); engine.Raisable = impl; diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockDelegateFactoryBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockDelegateFactoryBuilder.cs index 31dd19774e..026b05e8ee 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockDelegateFactoryBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockDelegateFactoryBuilder.cs @@ -27,7 +27,7 @@ public static string Build(MockTypeModel model) } writer.AppendLine(); - using (writer.Block($"private static global::TUnit.Mocks.Mock<{model.FullyQualifiedName}> Create(global::TUnit.Mocks.MockBehavior behavior)")) + using (writer.Block($"private static global::TUnit.Mocks.Mock<{model.FullyQualifiedName}> Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs)")) { writer.AppendLine($"var engine = new global::TUnit.Mocks.MockEngine<{model.FullyQualifiedName}>(behavior);"); diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockFactoryBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockFactoryBuilder.cs index 19bb2664e2..b612211185 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockFactoryBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockFactoryBuilder.cs @@ -19,7 +19,7 @@ public static string Build(MockTypeModel model) { BuildWrapFactory(writer, model, safeName); } - else if (model.IsPartialMock && !model.IsInterface) + else if (model.IsPartialMock) { BuildPartialFactory(writer, model, safeName); } @@ -60,8 +60,9 @@ private static void BuildInterfaceFactory(CodeWriter writer, MockTypeModel model } writer.AppendLine(); - using (writer.Block($"private static global::TUnit.Mocks.Mock<{mockableType}> Create(global::TUnit.Mocks.MockBehavior behavior)")) + using (writer.Block($"private static global::TUnit.Mocks.Mock<{mockableType}> Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs)")) { + writer.AppendLine($"if (constructorArgs.Length > 0) throw new global::System.ArgumentException($\"Interface mock '{mockableType}' does not support constructor arguments, but {{constructorArgs.Length}} were provided.\");"); writer.AppendLine($"var engine = new global::TUnit.Mocks.MockEngine<{mockableType}>(behavior);"); writer.AppendLine($"var impl = new {safeName}_MockImpl(engine);"); writer.AppendLine("engine.Raisable = impl;"); @@ -101,7 +102,7 @@ private static void BuildPartialFactory(CodeWriter writer, MockTypeModel model, writer.AppendLine("[global::System.Runtime.CompilerServices.ModuleInitializer]"); using (writer.Block("internal static void Register()")) { - writer.AppendLine($"global::TUnit.Mocks.Mock.RegisterPartialFactory<{model.FullyQualifiedName}>(Create);"); + writer.AppendLine($"global::TUnit.Mocks.Mock.RegisterFactory<{model.FullyQualifiedName}>(Create);"); } writer.AppendLine(); diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs index dff920976f..3289e65e88 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs @@ -20,7 +20,7 @@ public static string Build(MockTypeModel model) { BuildWrapMockImpl(writer, model, safeName); } - else if (model.IsPartialMock && !model.IsInterface) + else if (model.IsPartialMock) { BuildPartialMockImpl(writer, model, safeName); } diff --git a/TUnit.Mocks.SourceGenerator/Discovery/MockTypeDiscovery.cs b/TUnit.Mocks.SourceGenerator/Discovery/MockTypeDiscovery.cs index 29c768db9f..b964029fb0 100644 --- a/TUnit.Mocks.SourceGenerator/Discovery/MockTypeDiscovery.cs +++ b/TUnit.Mocks.SourceGenerator/Discovery/MockTypeDiscovery.cs @@ -11,12 +11,12 @@ namespace TUnit.Mocks.SourceGenerator.Discovery; internal static class MockTypeDiscovery { /// - /// Syntax predicate: quick check if a node might be a Mock.Of<T>(), Mock.OfPartial<T>(), - /// or MockRepository.Of<T>() call. Zero allocations - string comparison only. + /// Syntax predicate: quick check if a node might be a Mock.Of<T>(), + /// MockRepository.Of<T>(), etc. Zero allocations - string comparison only. /// public static bool IsMockOfInvocation(SyntaxNode node, CancellationToken ct) { - // Match: X.Of() or X.Of(behavior) or X.OfPartial(...) + // Match: X.Of() or X.Of(behavior, args) etc. // where X can be "Mock" (static) or a MockRepository variable (instance). // Also match X.Wrap(instance) where T is inferred (IdentifierNameSyntax). if (node is not InvocationExpressionSyntax invocation) @@ -29,7 +29,7 @@ public static bool IsMockOfInvocation(SyntaxNode node, CancellationToken ct) if (memberAccess.Name is GenericNameSyntax genericName) { var methodName = genericName.Identifier.ValueText; - return methodName is "Of" or "OfPartial" or "OfDelegate" or "Wrap"; + return methodName is "Of" or "OfDelegate" or "Wrap"; } // Simple name syntax: Mock.Wrap(instance) with type inference @@ -63,14 +63,12 @@ public static ImmutableArray TransformToModels(GeneratorSyntaxCon if (method is null) return ImmutableArray.Empty; - // Verify this is TUnit.Mocks.Mock.Of() / OfPartial() - // or TUnit.Mocks.MockRepository.Of() / OfPartial() + // Verify this is TUnit.Mocks.Mock.Of() or TUnit.Mocks.MockRepository.Of() var containingTypeName = method.ContainingType?.Name; if ((containingTypeName != "Mock" && containingTypeName != "MockRepository") || method.ContainingNamespace?.ToDisplayString() != "TUnit.Mocks") return ImmutableArray.Empty; - var isPartialMock = method.Name == "OfPartial"; var isDelegateMock = method.Name == "OfDelegate"; var isWrapMock = method.Name == "Wrap"; @@ -124,6 +122,9 @@ public static ImmutableArray TransformToModels(GeneratorSyntaxCon return ImmutableArray.Empty; // Single-type mock: build one model + transitive interface return types + // Partial mock behavior is determined by type kind — classes get partial mocks automatically. + var isPartialMock = namedType.TypeKind == TypeKind.Class; + if (method.TypeArguments.Length == 1) { var model = BuildSingleTypeModel(namedType, isPartialMock, compilationAssembly); @@ -397,7 +398,7 @@ public static ImmutableArray TransformGenerateMockAttribute( if (namedType.IsValueType) continue; - var model = BuildSingleTypeModel(namedType, isPartialMock: false, compilationAssembly); + var model = BuildSingleTypeModel(namedType, isPartialMock: namedType.TypeKind == TypeKind.Class, compilationAssembly); if (model is null) continue; diff --git a/TUnit.Mocks.Tests/MockRepositoryTests.cs b/TUnit.Mocks.Tests/MockRepositoryTests.cs index 84bb2e7246..072b09f291 100644 --- a/TUnit.Mocks.Tests/MockRepositoryTests.cs +++ b/TUnit.Mocks.Tests/MockRepositoryTests.cs @@ -190,13 +190,13 @@ public async Task Repository_VerifyAll_On_Empty_Repository_Does_Not_Throw() } [Test] - public async Task Repository_OfPartial_Creates_And_Tracks_Partial_Mock() + public async Task Repository_Of_ClassMock_Creates_And_Tracks_Partial_Mock() { // Arrange var repo = new MockRepository(); // Act - var mock = repo.OfPartial(); + var mock = repo.Of(); // Assert — tracked await Assert.That(repo.Mocks).Count().IsEqualTo(1); @@ -204,11 +204,11 @@ public async Task Repository_OfPartial_Creates_And_Tracks_Partial_Mock() } [Test] - public async Task Repository_OfPartial_Base_Method_Works() + public async Task Repository_Of_ClassMock_Base_Method_Works() { // Arrange var repo = new MockRepository(); - var mock = repo.OfPartial(); + var mock = repo.Of(); // Act — unconfigured, calls base implementation var result = mock.Object.Add(3, 4); @@ -218,11 +218,11 @@ public async Task Repository_OfPartial_Base_Method_Works() } [Test] - public async Task Repository_OfPartial_Configured_Method_Returns_Mock_Value() + public async Task Repository_Of_ClassMock_Configured_Method_Returns_Mock_Value() { // Arrange var repo = new MockRepository(); - var mock = repo.OfPartial(); + var mock = repo.Of(); mock.Greet(Any()).Returns("Mocked!"); // Act @@ -233,11 +233,11 @@ public async Task Repository_OfPartial_Configured_Method_Returns_Mock_Value() } [Test] - public async Task Repository_OfPartial_With_Constructor_Args() + public async Task Repository_Of_ClassMock_With_Constructor_Args() { // Arrange var repo = new MockRepository(); - var mock = repo.OfPartial("PREFIX"); + var mock = repo.Of("PREFIX"); mock.Format(Any()).Returns("formatted"); // Act — GetPrefix is virtual, unconfigured → calls base @@ -248,12 +248,12 @@ public async Task Repository_OfPartial_With_Constructor_Args() } [Test] - public async Task Repository_OfPartial_VerifyAll_Includes_Partial_Mocks() + public async Task Repository_Of_ClassMock_VerifyAll_Includes_Partial_Mocks() { // Arrange var repo = new MockRepository(); var serviceMock = repo.Of(); - var partialMock = repo.OfPartial(); + var partialMock = repo.Of(); serviceMock.GetData(Any()).Returns("data"); partialMock.Greet(Any()).Returns("Hi"); @@ -267,22 +267,22 @@ public async Task Repository_OfPartial_VerifyAll_Includes_Partial_Mocks() } [Test] - public async Task Repository_OfPartial_With_Strict_Behavior() + public async Task Repository_Of_ClassMock_With_Strict_Behavior() { // Arrange var repo = new MockRepository(MockBehavior.Strict); - var mock = repo.OfPartial(); + var mock = repo.Of(); // Assert — partial mock inherits strict behavior from repository await Assert.That(Mock.GetBehavior(mock)).IsEqualTo(MockBehavior.Strict); } [Test] - public async Task Repository_OfPartial_Behavior_Override() + public async Task Repository_Of_ClassMock_Behavior_Override() { // Arrange — strict repository, loose partial mock var repo = new MockRepository(MockBehavior.Strict); - var mock = repo.OfPartial(MockBehavior.Loose); + var mock = repo.Of(MockBehavior.Loose); // Assert — behavior overridden await Assert.That(Mock.GetBehavior(mock)).IsEqualTo(MockBehavior.Loose); diff --git a/TUnit.Mocks.Tests/PartialMockTests.cs b/TUnit.Mocks.Tests/PartialMockTests.cs index ed3e509753..35e76e929a 100644 --- a/TUnit.Mocks.Tests/PartialMockTests.cs +++ b/TUnit.Mocks.Tests/PartialMockTests.cs @@ -40,10 +40,10 @@ protected ServiceWithConstructor(string prefix) public class PartialMockTests { [Test] - public async Task OfPartial_Abstract_Class_Creates_Instance() + public async Task Of_Abstract_Class_Creates_Instance() { // Arrange & Act - var mock = Mock.OfPartial(); + var mock = Mock.Of(); // Assert await Assert.That(mock).IsNotNull(); @@ -54,7 +54,7 @@ public async Task OfPartial_Abstract_Class_Creates_Instance() public async Task Abstract_Method_Returns_Configured_Value() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("TestName"); // Act @@ -68,7 +68,7 @@ public async Task Abstract_Method_Returns_Configured_Value() public async Task Unconfigured_Virtual_Method_Calls_Base_Implementation() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); // GetName is abstract - configure it mock.GetName().Returns("TestName"); // Calculate is virtual - do NOT configure it @@ -84,7 +84,7 @@ public async Task Unconfigured_Virtual_Method_Calls_Base_Implementation() public async Task Configured_Virtual_Method_Returns_Override_Instead_Of_Base() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("TestName"); mock.Calculate(Any()).Returns(42); @@ -99,7 +99,7 @@ public async Task Configured_Virtual_Method_Returns_Override_Instead_Of_Base() public async Task Concrete_Class_Partial_Mock_Calls_Base_When_Not_Configured() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); // Act - no setup, should call base var result = mock.Object.Greet("World"); @@ -112,7 +112,7 @@ public async Task Concrete_Class_Partial_Mock_Calls_Base_When_Not_Configured() public async Task Concrete_Class_Override_Returns_Configured_Value() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.Greet(Any()).Returns("Mocked!"); // Act @@ -126,7 +126,7 @@ public async Task Concrete_Class_Override_Returns_Configured_Value() public async Task Concrete_Class_Multiple_Methods_Mixed_Setup() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.Greet("Alice").Returns("Hi Alice!"); // Don't setup Add - let it call base @@ -143,7 +143,7 @@ public async Task Concrete_Class_Multiple_Methods_Mixed_Setup() public async Task Constructor_Args_Passed_To_Base() { // Arrange - var mock = Mock.OfPartial("PREFIX"); + var mock = Mock.Of("PREFIX"); mock.Format(Any()).Returns("formatted"); // Act - GetPrefix is virtual and unconfigured, so calls base which uses _prefix @@ -157,7 +157,7 @@ public async Task Constructor_Args_Passed_To_Base() public async Task Constructor_Args_Abstract_Method_Override() { // Arrange - var mock = Mock.OfPartial("test"); + var mock = Mock.Of("test"); mock.Format("value").Returns("test:value"); // Act @@ -171,7 +171,7 @@ public async Task Constructor_Args_Abstract_Method_Override() public async Task Non_Virtual_Method_Still_Works() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("Name"); // Act - NonVirtualMethod is not virtual, so it runs the original implementation @@ -185,7 +185,7 @@ public async Task Non_Virtual_Method_Still_Works() public void Verify_Calls_On_Partial_Mock() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.Greet(Any()).Returns("Hi"); // Act @@ -200,7 +200,7 @@ public void Verify_Calls_On_Partial_Mock() public void Verify_Calls_On_Unconfigured_Virtual_Method() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); // Act - unconfigured, calls base mock.Object.Add(1, 2); @@ -214,7 +214,7 @@ public void Verify_Calls_On_Unconfigured_Virtual_Method() public async Task Partial_Mock_With_Strict_Behavior_Abstract_Method_Throws_Without_Setup() { // Arrange - var mock = Mock.OfPartial(MockBehavior.Strict); + var mock = Mock.Of(MockBehavior.Strict); // Act & Assert - abstract method with no setup in strict mode should throw var exception = Assert.Throws(() => @@ -229,7 +229,7 @@ public async Task Partial_Mock_With_Strict_Behavior_Abstract_Method_Throws_Witho public async Task Partial_Mock_With_Strict_Behavior_Virtual_Method_Calls_Base() { // Arrange - strict mode, but virtual methods should still call base when unconfigured - var mock = Mock.OfPartial(MockBehavior.Strict); + var mock = Mock.Of(MockBehavior.Strict); // Act - virtual method with no setup should call base (not throw) var result = mock.Object.Add(2, 3); diff --git a/TUnit.Mocks.Tests/ProtectedMemberTests.cs b/TUnit.Mocks.Tests/ProtectedMemberTests.cs index 1a2ea35956..086b2a47d6 100644 --- a/TUnit.Mocks.Tests/ProtectedMemberTests.cs +++ b/TUnit.Mocks.Tests/ProtectedMemberTests.cs @@ -32,7 +32,7 @@ public class ProtectedMemberTests public async Task Protected_Virtual_Method_Calls_Base_When_Not_Configured() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("Test"); mock.FormatResult(Any()).Returns("formatted"); @@ -48,7 +48,7 @@ public async Task Protected_Virtual_Method_Calls_Base_When_Not_Configured() public async Task Protected_Virtual_Method_Can_Be_Configured() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("Test"); mock.ComputeValue(Any()).Returns(42); mock.FormatResult(Any()).Returns("configured"); @@ -65,7 +65,7 @@ public async Task Protected_Virtual_Method_Can_Be_Configured() public async Task Protected_Abstract_Method_Can_Be_Configured() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("Test"); mock.FormatResult(Any()).Returns("custom format"); @@ -80,7 +80,7 @@ public async Task Protected_Abstract_Method_Can_Be_Configured() public async Task Protected_Method_Calls_Are_Recorded_In_Invocations() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("Test"); mock.FormatResult(Any()).Returns("result"); @@ -95,7 +95,7 @@ public async Task Protected_Method_Calls_Are_Recorded_In_Invocations() public async Task Protected_Method_Can_Be_Verified() { // Arrange - var mock = Mock.OfPartial(); + var mock = Mock.Of(); mock.GetName().Returns("Test"); mock.FormatResult(Any()).Returns("result"); diff --git a/TUnit.Mocks/Mock.cs b/TUnit.Mocks/Mock.cs index 2553ea2e6d..0e26fc8621 100644 --- a/TUnit.Mocks/Mock.cs +++ b/TUnit.Mocks/Mock.cs @@ -12,16 +12,14 @@ public static class Mock // The source generator registers factories via this method at module initialization time. // ConcurrentDictionary is used because module initializers from multiple assemblies // can run concurrently when test assemblies are loaded in parallel. - private static readonly ConcurrentDictionary> _factories = new(); - - // Separate registry for partial mock factories that accept constructor args. - private static readonly ConcurrentDictionary> _partialFactories = new(); + // All factories (interface, class, abstract) use a unified signature with constructor args. + private static readonly ConcurrentDictionary> _factories = new(); // Registry for multi-interface mock factories, keyed by compound type string. - private static readonly ConcurrentDictionary> _multiFactories = new(); + private static readonly ConcurrentDictionary> _multiFactories = new(); // Separate registry for delegate mock factories. - private static readonly ConcurrentDictionary> _delegateFactories = new(); + private static readonly ConcurrentDictionary> _delegateFactories = new(); // Separate registry for wrap mock factories that accept a real instance. private static readonly ConcurrentDictionary> _wrapFactories = new(); @@ -61,27 +59,17 @@ public static Mock Get(T mockedObject) where T : class /// Not intended for direct use. /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static void RegisterFactory(Func> factory) where T : class + public static void RegisterFactory(Func> factory) where T : class { _factories[typeof(T)] = factory; } - /// - /// Registers a factory for creating partial mocks of type T. Called by generated code. - /// Not intended for direct use. - /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static void RegisterPartialFactory(Func> factory) where T : class - { - _partialFactories[typeof(T)] = factory; - } - /// /// Registers a factory for creating multi-interface mocks. Called by generated code. /// Not intended for direct use. /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static void RegisterMultiFactory(string key, Func factory) + public static void RegisterMultiFactory(string key, Func factory) { _multiFactories[key] = factory; } @@ -91,7 +79,7 @@ public static void RegisterMultiFactory(string key, Func f /// Not intended for direct use. /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public static void RegisterDelegateFactory(Func> factory) where T : class + public static void RegisterDelegateFactory(Func> factory) where T : class { _delegateFactories[typeof(T)] = factory; } @@ -119,32 +107,23 @@ public static Mock Of(MockBehavior behavior, IDefaultValueProvider default /// Creates a mock of T with specified behavior. public static Mock Of(MockBehavior behavior) where T : class - { - if (_factories.TryGetValue(typeof(T), out var factory)) - { - return (Mock)factory(behavior); - } - - throw new InvalidOperationException( - $"No mock factory registered for type '{typeof(T).FullName}'. " + - $"Ensure the TUnit.Mocks source generator is referenced in your project."); - } + => Of(behavior, Array.Empty()); - /// Creates a partial mock of T in loose mode, optionally passing constructor arguments. - public static Mock OfPartial(params object[] constructorArgs) where T : class - => OfPartial(MockBehavior.Loose, constructorArgs); + /// Creates a mock of T in loose mode, optionally passing constructor arguments for concrete classes. + public static Mock Of(params object[] constructorArgs) where T : class + => Of(MockBehavior.Loose, constructorArgs); - /// Creates a partial mock of T with specified behavior, optionally passing constructor arguments. - public static Mock OfPartial(MockBehavior behavior, params object[] constructorArgs) where T : class + /// Creates a mock of T with specified behavior, optionally passing constructor arguments for concrete classes. + public static Mock Of(MockBehavior behavior, params object[] constructorArgs) where T : class { - if (_partialFactories.TryGetValue(typeof(T), out var factory)) + if (_factories.TryGetValue(typeof(T), out var factory)) { return (Mock)factory(behavior, constructorArgs); } throw new InvalidOperationException( - $"No partial mock factory registered for type '{typeof(T).FullName}'. " + - $"Ensure the TUnit.Mocks source generator is referenced and the type is not sealed."); + $"No mock factory registered for type '{typeof(T).FullName}'. " + + $"Ensure the TUnit.Mocks source generator is referenced in your project."); } /// Creates a delegate mock of T in loose mode. @@ -163,7 +142,7 @@ public static Mock OfDelegate(MockBehavior behavior) where T : class { if (_delegateFactories.TryGetValue(typeof(T), out var factory)) { - return (Mock)factory(behavior); + return (Mock)factory(behavior, Array.Empty()); } throw new InvalidOperationException( @@ -199,7 +178,7 @@ public static Mock Of(MockBehavior behavior) var key = GetMultiKey(typeof(T1), typeof(T2)); if (_multiFactories.TryGetValue(key, out var factory)) { - return (Mock)factory(behavior); + return (Mock)factory(behavior, Array.Empty()); } throw new InvalidOperationException( @@ -219,7 +198,7 @@ public static Mock Of(MockBehavior behavior) var key = GetMultiKey(typeof(T1), typeof(T2), typeof(T3)); if (_multiFactories.TryGetValue(key, out var factory)) { - return (Mock)factory(behavior); + return (Mock)factory(behavior, Array.Empty()); } throw new InvalidOperationException( @@ -239,7 +218,7 @@ public static Mock Of(MockBehavior behavior) var key = GetMultiKey(typeof(T1), typeof(T2), typeof(T3), typeof(T4)); if (_multiFactories.TryGetValue(key, out var factory)) { - return (Mock)factory(behavior); + return (Mock)factory(behavior, Array.Empty()); } throw new InvalidOperationException( @@ -257,7 +236,7 @@ public static bool TryCreateAutoMock(Type type, MockBehavior behavior, out IMock { if (_factories.TryGetValue(type, out var factory)) { - var mock = (IMock)factory(behavior); + var mock = (IMock)factory(behavior, Array.Empty()); mockWrapper = mock; return true; } diff --git a/TUnit.Mocks/MockRepository.cs b/TUnit.Mocks/MockRepository.cs index 70b14d47b3..c99a2e56cf 100644 --- a/TUnit.Mocks/MockRepository.cs +++ b/TUnit.Mocks/MockRepository.cs @@ -24,20 +24,16 @@ public MockRepository(MockBehavior defaultBehavior) /// Creates and tracks a mock of T with the specified behavior. public Mock Of(MockBehavior behavior) where T : class - { - var mock = Mock.Of(behavior); - Track(mock); - return mock; - } + => Of(behavior, Array.Empty()); - /// Creates and tracks a partial mock of T using the repository's default behavior. - public Mock OfPartial(params object[] constructorArgs) where T : class - => OfPartial(_defaultBehavior, constructorArgs); + /// Creates and tracks a mock of T using the repository's default behavior, optionally passing constructor arguments for concrete classes. + public Mock Of(params object[] constructorArgs) where T : class + => Of(_defaultBehavior, constructorArgs); - /// Creates and tracks a partial mock of T with the specified behavior. - public Mock OfPartial(MockBehavior behavior, params object[] constructorArgs) where T : class + /// Creates and tracks a mock of T with the specified behavior, optionally passing constructor arguments for concrete classes. + public Mock Of(MockBehavior behavior, params object[] constructorArgs) where T : class { - var mock = Mock.OfPartial(behavior, constructorArgs); + var mock = Mock.Of(behavior, constructorArgs); Track(mock); return mock; } diff --git a/docs/docs/writing-tests/mocking/advanced.md b/docs/docs/writing-tests/mocking/advanced.md index bf4c594731..35332602d3 100644 --- a/docs/docs/writing-tests/mocking/advanced.md +++ b/docs/docs/writing-tests/mocking/advanced.md @@ -169,7 +169,7 @@ repo.Reset(); // clear all mocks |---|---| | `repo.Of()` | Create and track a loose mock | | `repo.Of(behavior)` | Create and track a mock with specific behavior | -| `repo.OfPartial(args)` | Create and track a partial mock | +| `repo.Of(args)` | Create and track a mock with constructor args | | `repo.Track(existingMock)` | Add an existing mock to the repository | | `repo.Mocks` | All tracked mocks | | `repo.VerifyAll()` | Verify all setups on all mocks | diff --git a/docs/docs/writing-tests/mocking/index.md b/docs/docs/writing-tests/mocking/index.md index d13ae376f8..533db40ec2 100644 --- a/docs/docs/writing-tests/mocking/index.md +++ b/docs/docs/writing-tests/mocking/index.md @@ -69,11 +69,11 @@ public class GreeterTests | Factory Method | Use Case | |---|---| -| `Mock.Of()` | Mock an interface or abstract class | -| `Mock.OfPartial(args)` | Mock a concrete class (calls base for unconfigured methods) | +| `Mock.Of()` | Mock an interface, abstract class, or concrete class | | `Mock.OfDelegate()` | Mock a delegate (`Func<>`, `Action<>`, etc.) | | `Mock.Wrap(instance)` | Wrap a real object with selective overrides | | `Mock.Of()` | Mock multiple interfaces on a single object | +| `[GenerateMock(typeof(T))]` | Generate a mock for interfaces with static abstract members ([details](setup#interfaces-with-static-abstract-members)) | | `Mock.HttpHandler()` | Create a `MockHttpHandler` *(requires `TUnit.Mocks.Http`)* | | `Mock.HttpClient(baseAddress?)` | Create a `MockHttpClient` — an `HttpClient` with a `.Handler` property *(requires `TUnit.Mocks.Http`)* | | `Mock.Logger()` | Create a `MockLogger` *(requires `TUnit.Mocks.Logging`)* | diff --git a/docs/docs/writing-tests/mocking/setup.md b/docs/docs/writing-tests/mocking/setup.md index 5f392f26c6..44bf8d4dd0 100644 --- a/docs/docs/writing-tests/mocking/setup.md +++ b/docs/docs/writing-tests/mocking/setup.md @@ -167,7 +167,7 @@ public abstract class Calculator public abstract int Multiply(int a, int b); } -var mock = Mock.OfPartial(); +var mock = Mock.Of(); mock.Multiply(Any(), Any()).Returns(99); mock.Object.Add(2, 3); // 5 (base implementation) @@ -177,9 +177,32 @@ mock.Object.Multiply(2, 3); // 99 (mocked) Pass constructor arguments for non-default constructors: ```csharp -var mock = Mock.OfPartial("connectionString", 42); +var mock = Mock.Of("connectionString", 42); ``` +## Interfaces with Static Abstract Members + +Interfaces that have static abstract members (directly or inherited) cannot be used as type arguments in `Mock.Of()` — the compiler raises **CS8920** before the source generator runs. + +Use `[assembly: GenerateMock(typeof(T))]` to work around this. The source generator produces a bridge interface (suffixed `_Mockable`) that provides default implementations for the static abstract members: + +```csharp +using TUnit.Mocks; + +[assembly: GenerateMock(typeof(IMyParseable))] + +public interface IMyParseable : IParsable +{ + string Format(); +} + +// In your test — use the generated bridge type: +var mock = Mock.Of(); +mock.Format().Returns("formatted"); +``` + +The bridge type implements all the non-static members of the original interface, so you can set up and verify calls as normal. + ## Delegate Mocking Mock any delegate type: