From 2c31e4425f22ce892dc924de5ef1b071ea7af5e6 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:15:53 +0000 Subject: [PATCH 1/4] feat(mocks): generate Func overloads for inline lambda predicates Enable inline lambda syntax in mock setup/verification by generating additional extension method overloads where eligible Arg parameters are replaced with Func. This sidesteps CS1660 (lambdas cannot implicitly convert to non-delegate types) while reusing the existing implicit Arg(Func) conversion internally. For N eligible parameters, generates all 2^N - 1 overloads (capped at N=8). Eligible parameters are In/In_Readonly direction, non-ref-struct. Out and Ref parameters are excluded. --- ...nheriting_Multiple_Interfaces.verified.txt | 7 + .../Interface_With_Async_Methods.verified.txt | 21 ++ .../Interface_With_Events.verified.txt | 7 + ...nterface_With_Generic_Methods.verified.txt | 21 ++ .../Interface_With_Mixed_Members.verified.txt | 14 ++ ...rface_With_Out_Ref_Parameters.verified.txt | 7 + ...rface_With_Overloaded_Methods.verified.txt | 90 ++++++++ .../Multi_Method_Interface.verified.txt | 44 ++++ ...ple_Interface_With_One_Method.verified.txt | 7 + .../Builders/MockMembersBuilder.cs | 132 ++++++++++++ TUnit.Mocks.Tests/FuncOverloadTests.cs | 201 ++++++++++++++++++ docs/docs/guides/cookbook.md | 123 +++++------ .../mocking/argument-matchers.md | 109 ++++++---- docs/docs/test-authoring/mocking/index.md | 30 ++- 14 files changed, 710 insertions(+), 103 deletions(-) create mode 100644 TUnit.Mocks.Tests/FuncOverloadTests.cs 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 a9052e800a..ba17896519 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 @@ -89,6 +89,13 @@ namespace TUnit.Mocks.Generated var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { data.Matcher }; return new IReadWriter_Write_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Write", matchers); } + + public static IReadWriter_Write_M2_MockCall Write(this global::TUnit.Mocks.Mock mock, global::System.Func data) + { + global::TUnit.Mocks.Arguments.Arg __fa_data = data; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_data.Matcher }; + return new IReadWriter_Write_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Write", matchers); + } } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 4f0d0c19f3..54debdf4bf 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 @@ -115,6 +115,13 @@ namespace TUnit.Mocks.Generated return new IAsyncService_GetValueAsync_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "GetValueAsync", matchers); } + public static IAsyncService_GetValueAsync_M0_MockCall GetValueAsync(this global::TUnit.Mocks.Mock mock, global::System.Func key) + { + global::TUnit.Mocks.Arguments.Arg __fa_key = key; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_key.Matcher }; + return new IAsyncService_GetValueAsync_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "GetValueAsync", matchers); + } + public static global::TUnit.Mocks.VoidMockMethodCall DoWorkAsync(this global::TUnit.Mocks.Mock mock) { var matchers = global::System.Array.Empty(); @@ -127,11 +134,25 @@ namespace TUnit.Mocks.Generated return new IAsyncService_ComputeAsync_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "ComputeAsync", matchers); } + public static IAsyncService_ComputeAsync_M2_MockCall ComputeAsync(this global::TUnit.Mocks.Mock mock, global::System.Func input) + { + global::TUnit.Mocks.Arguments.Arg __fa_input = input; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_input.Matcher }; + return new IAsyncService_ComputeAsync_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "ComputeAsync", matchers); + } + public static IAsyncService_InitializeAsync_M3_MockCall InitializeAsync(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg ct) { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { ct.Matcher }; return new IAsyncService_InitializeAsync_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "InitializeAsync", matchers); } + + public static IAsyncService_InitializeAsync_M3_MockCall InitializeAsync(this global::TUnit.Mocks.Mock mock, global::System.Func ct) + { + global::TUnit.Mocks.Arguments.Arg __fa_ct = ct; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_ct.Matcher }; + return new IAsyncService_InitializeAsync_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "InitializeAsync", matchers); + } } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 4f8bac42ce..364ed98c68 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt @@ -120,6 +120,13 @@ namespace TUnit.Mocks.Generated return new INotifier_Notify_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Notify", matchers); } + public static INotifier_Notify_M0_MockCall Notify(this global::TUnit.Mocks.Mock mock, global::System.Func message) + { + global::TUnit.Mocks.Arguments.Arg __fa_message = message; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_message.Matcher }; + return new INotifier_Notify_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Notify", matchers); + } + public static void RaiseItemAdded(this global::TUnit.Mocks.Mock mock, string e) { ((global::TUnit.Mocks.IRaisable)global::TUnit.Mocks.Mock.GetEngine(mock).Raisable!).RaiseEvent("ItemAdded", (object?)e); 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 200e8989ca..eb876fb232 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 @@ -78,16 +78,37 @@ namespace TUnit.Mocks.Generated return new global::TUnit.Mocks.MockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "GetById", matchers); } + public static global::TUnit.Mocks.MockMethodCall GetById(this global::TUnit.Mocks.Mock mock, global::System.Func id) where T : class + { + global::TUnit.Mocks.Arguments.Arg __fa_id = id; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_id.Matcher }; + return new global::TUnit.Mocks.MockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "GetById", matchers); + } + public static global::TUnit.Mocks.VoidMockMethodCall Save(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg entity) where T : class, new() { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { entity.Matcher }; return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Save", matchers); } + public static global::TUnit.Mocks.VoidMockMethodCall Save(this global::TUnit.Mocks.Mock mock, global::System.Func entity) where T : class, new() + { + global::TUnit.Mocks.Arguments.Arg __fa_entity = entity; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_entity.Matcher }; + return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Save", matchers); + } + public static global::TUnit.Mocks.MockMethodCall Transform(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg input) where TInput : notnull where TResult : struct { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { input.Matcher }; return new global::TUnit.Mocks.MockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Transform", matchers); } + + public static global::TUnit.Mocks.MockMethodCall Transform(this global::TUnit.Mocks.Mock mock, global::System.Func input) where TInput : notnull where TResult : struct + { + global::TUnit.Mocks.Arguments.Arg __fa_input = input; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_input.Matcher }; + return new global::TUnit.Mocks.MockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Transform", matchers); + } } } 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 c8ea45824d..8b8c4a4c35 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 @@ -144,12 +144,26 @@ namespace TUnit.Mocks.Generated return new IService_GetAsync_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "GetAsync", matchers); } + public static IService_GetAsync_M3_MockCall GetAsync(this global::TUnit.Mocks.Mock mock, global::System.Func id) + { + global::TUnit.Mocks.Arguments.Arg __fa_id = id; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_id.Matcher }; + return new IService_GetAsync_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "GetAsync", matchers); + } + public static IService_Process_M4_MockCall Process(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg data) { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { data.Matcher }; return new IService_Process_M4_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 4, "Process", matchers); } + public static IService_Process_M4_MockCall Process(this global::TUnit.Mocks.Mock mock, global::System.Func data) + { + global::TUnit.Mocks.Arguments.Arg __fa_data = data; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_data.Matcher }; + return new IService_Process_M4_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 4, "Process", matchers); + } + extension(global::TUnit.Mocks.Mock mock) { public global::TUnit.Mocks.PropertyMockCall Name 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 54ea3591ec..9271350b0a 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 @@ -86,6 +86,13 @@ namespace TUnit.Mocks.Generated return new IDictionary_TryGetValue_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "TryGetValue", matchers); } + public static IDictionary_TryGetValue_M0_MockCall TryGetValue(this global::TUnit.Mocks.Mock mock, global::System.Func key) + { + global::TUnit.Mocks.Arguments.Arg __fa_key = key; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_key.Matcher }; + return new IDictionary_TryGetValue_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "TryGetValue", matchers); + } + public static IDictionary_Swap_M1_MockCall Swap(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg a, global::TUnit.Mocks.Arguments.Arg b) { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { a.Matcher, b.Matcher }; 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 3ff1a23f23..27371502d5 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 @@ -83,23 +83,113 @@ namespace TUnit.Mocks.Generated return new IFormatter_Format_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Format", matchers); } + public static IFormatter_Format_M0_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func value) + { + global::TUnit.Mocks.Arguments.Arg __fa_value = value; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_value.Matcher }; + return new IFormatter_Format_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Format", matchers); + } + public static IFormatter_Format_M1_MockCall Format(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg value) { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { value.Matcher }; return new IFormatter_Format_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Format", matchers); } + public static IFormatter_Format_M1_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func value) + { + global::TUnit.Mocks.Arguments.Arg __fa_value = value; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_value.Matcher }; + return new IFormatter_Format_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Format", matchers); + } + public static IFormatter_Format_M2_MockCall Format(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg template, global::TUnit.Mocks.Arguments.Arg arg1) { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { template.Matcher, arg1.Matcher }; return new IFormatter_Format_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Format", matchers); } + public static IFormatter_Format_M2_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func template, global::TUnit.Mocks.Arguments.Arg arg1) + { + global::TUnit.Mocks.Arguments.Arg __fa_template = template; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_template.Matcher, arg1.Matcher }; + return new IFormatter_Format_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Format", matchers); + } + + public static IFormatter_Format_M2_MockCall Format(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg template, global::System.Func arg1) + { + global::TUnit.Mocks.Arguments.Arg __fa_arg1 = arg1; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { template.Matcher, __fa_arg1.Matcher }; + return new IFormatter_Format_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Format", matchers); + } + + public static IFormatter_Format_M2_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func template, global::System.Func arg1) + { + global::TUnit.Mocks.Arguments.Arg __fa_template = template; + global::TUnit.Mocks.Arguments.Arg __fa_arg1 = arg1; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_template.Matcher, __fa_arg1.Matcher }; + return new IFormatter_Format_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Format", matchers); + } + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg template, global::TUnit.Mocks.Arguments.Arg arg1, global::TUnit.Mocks.Arguments.Arg arg2) { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { template.Matcher, arg1.Matcher, arg2.Matcher }; return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); } + + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func template, global::TUnit.Mocks.Arguments.Arg arg1, global::TUnit.Mocks.Arguments.Arg arg2) + { + global::TUnit.Mocks.Arguments.Arg __fa_template = template; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_template.Matcher, arg1.Matcher, arg2.Matcher }; + return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); + } + + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg template, global::System.Func arg1, global::TUnit.Mocks.Arguments.Arg arg2) + { + global::TUnit.Mocks.Arguments.Arg __fa_arg1 = arg1; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { template.Matcher, __fa_arg1.Matcher, arg2.Matcher }; + return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); + } + + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func template, global::System.Func arg1, global::TUnit.Mocks.Arguments.Arg arg2) + { + global::TUnit.Mocks.Arguments.Arg __fa_template = template; + global::TUnit.Mocks.Arguments.Arg __fa_arg1 = arg1; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_template.Matcher, __fa_arg1.Matcher, arg2.Matcher }; + return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); + } + + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg template, global::TUnit.Mocks.Arguments.Arg arg1, global::System.Func arg2) + { + global::TUnit.Mocks.Arguments.Arg __fa_arg2 = arg2; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { template.Matcher, arg1.Matcher, __fa_arg2.Matcher }; + return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); + } + + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func template, global::TUnit.Mocks.Arguments.Arg arg1, global::System.Func arg2) + { + global::TUnit.Mocks.Arguments.Arg __fa_template = template; + global::TUnit.Mocks.Arguments.Arg __fa_arg2 = arg2; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_template.Matcher, arg1.Matcher, __fa_arg2.Matcher }; + return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); + } + + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg template, global::System.Func arg1, global::System.Func arg2) + { + global::TUnit.Mocks.Arguments.Arg __fa_arg1 = arg1; + global::TUnit.Mocks.Arguments.Arg __fa_arg2 = arg2; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { template.Matcher, __fa_arg1.Matcher, __fa_arg2.Matcher }; + return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); + } + + public static IFormatter_Format_M3_MockCall Format(this global::TUnit.Mocks.Mock mock, global::System.Func template, global::System.Func arg1, global::System.Func arg2) + { + global::TUnit.Mocks.Arguments.Arg __fa_template = template; + global::TUnit.Mocks.Arguments.Arg __fa_arg1 = arg1; + global::TUnit.Mocks.Arguments.Arg __fa_arg2 = arg2; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_template.Matcher, __fa_arg1.Matcher, __fa_arg2.Matcher }; + return new IFormatter_Format_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "Format", matchers); + } } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 27477809c6..197f3d5a86 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt @@ -78,12 +78,56 @@ namespace TUnit.Mocks.Generated return new ICalculator_Add_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Add", matchers); } + public static ICalculator_Add_M0_MockCall Add(this global::TUnit.Mocks.Mock mock, global::System.Func a, global::TUnit.Mocks.Arguments.Arg b) + { + global::TUnit.Mocks.Arguments.Arg __fa_a = a; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_a.Matcher, b.Matcher }; + return new ICalculator_Add_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Add", matchers); + } + + public static ICalculator_Add_M0_MockCall Add(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg a, global::System.Func b) + { + global::TUnit.Mocks.Arguments.Arg __fa_b = b; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { a.Matcher, __fa_b.Matcher }; + return new ICalculator_Add_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Add", matchers); + } + + public static ICalculator_Add_M0_MockCall Add(this global::TUnit.Mocks.Mock mock, global::System.Func a, global::System.Func b) + { + global::TUnit.Mocks.Arguments.Arg __fa_a = a; + global::TUnit.Mocks.Arguments.Arg __fa_b = b; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_a.Matcher, __fa_b.Matcher }; + return new ICalculator_Add_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Add", matchers); + } + public static ICalculator_Subtract_M1_MockCall Subtract(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg a, global::TUnit.Mocks.Arguments.Arg b) { var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { a.Matcher, b.Matcher }; return new ICalculator_Subtract_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Subtract", matchers); } + public static ICalculator_Subtract_M1_MockCall Subtract(this global::TUnit.Mocks.Mock mock, global::System.Func a, global::TUnit.Mocks.Arguments.Arg b) + { + global::TUnit.Mocks.Arguments.Arg __fa_a = a; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_a.Matcher, b.Matcher }; + return new ICalculator_Subtract_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Subtract", matchers); + } + + public static ICalculator_Subtract_M1_MockCall Subtract(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg a, global::System.Func b) + { + global::TUnit.Mocks.Arguments.Arg __fa_b = b; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { a.Matcher, __fa_b.Matcher }; + return new ICalculator_Subtract_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Subtract", matchers); + } + + public static ICalculator_Subtract_M1_MockCall Subtract(this global::TUnit.Mocks.Mock mock, global::System.Func a, global::System.Func b) + { + global::TUnit.Mocks.Arguments.Arg __fa_a = a; + global::TUnit.Mocks.Arguments.Arg __fa_b = b; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_a.Matcher, __fa_b.Matcher }; + return new ICalculator_Subtract_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "Subtract", matchers); + } + public static global::TUnit.Mocks.VoidMockMethodCall Reset(this global::TUnit.Mocks.Mock mock) { var matchers = global::System.Array.Empty(); 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 b054d42815..3077ce0d93 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 @@ -67,6 +67,13 @@ namespace TUnit.Mocks.Generated var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { name.Matcher }; return new IGreeter_Greet_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Greet", matchers); } + + public static IGreeter_Greet_M0_MockCall Greet(this global::TUnit.Mocks.Mock mock, global::System.Func name) + { + global::TUnit.Mocks.Arguments.Arg __fa_name = name; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_name.Matcher }; + return new IGreeter_Greet_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Greet", matchers); + } } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs index dd8aaf9be6..9d948177fc 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs @@ -516,13 +516,16 @@ private static void GenerateMemberMethod(CodeWriter writer, MockMemberModel meth { writer.AppendLine("#if NET9_0_OR_GREATER"); EmitMemberMethodBody(writer, method, model, safeName, includeRefStructArgs: true); + EmitFuncOverloads(writer, method, model, safeName, includeRefStructArgs: true); writer.AppendLine("#else"); EmitMemberMethodBody(writer, method, model, safeName, includeRefStructArgs: false); + EmitFuncOverloads(writer, method, model, safeName, includeRefStructArgs: false); writer.AppendLine("#endif"); } else { EmitMemberMethodBody(writer, method, model, safeName, includeRefStructArgs: false); + EmitFuncOverloads(writer, method, model, safeName, includeRefStructArgs: false); } } @@ -593,6 +596,135 @@ private static void EmitMemberMethodBody(CodeWriter writer, MockMemberModel meth } } + private static List GetFuncEligibleParamIndices(MockMemberModel method) + { + var indices = new List(); + for (int i = 0; i < method.Parameters.Length; i++) + { + var p = method.Parameters[i]; + if ((p.Direction == ParameterDirection.In || p.Direction == ParameterDirection.In_Readonly) + && !p.IsRefStruct) + { + indices.Add(i); + } + } + return indices; + } + + private static void EmitFuncOverloads(CodeWriter writer, MockMemberModel method, MockTypeModel model, + string safeName, bool includeRefStructArgs) + { + var eligible = GetFuncEligibleParamIndices(method); + if (eligible.Count == 0 || eligible.Count > MaxTypedParams) return; + + int totalMasks = (1 << eligible.Count) - 1; + for (int mask = 1; mask <= totalMasks; mask++) + { + writer.AppendLine(); + EmitSingleFuncOverload(writer, method, model, safeName, eligible, mask, includeRefStructArgs); + } + } + + private static void EmitSingleFuncOverload(CodeWriter writer, MockMemberModel method, MockTypeModel model, + string safeName, List eligibleIndices, int funcMask, bool includeRefStructArgs) + { + // Determine which parameter indices use Func + var funcIndices = new HashSet(); + for (int bit = 0; bit < eligibleIndices.Count; bit++) + { + if ((funcMask & (1 << bit)) != 0) + funcIndices.Add(eligibleIndices[bit]); + } + + // Return type (same logic as EmitMemberMethodBody) + var setupReturnType = method.IsAsync && !method.IsVoid ? method.UnwrappedReturnType : method.ReturnType; + var hasEvents = model.Events.Length > 0; + var useTypedWrapper = ShouldGenerateTypedWrapper(method, hasEvents); + + string returnType; + if (useTypedWrapper) + returnType = GetWrapperName(safeName, method); + else if (method.IsVoid || method.IsRefStructReturn) + returnType = "global::TUnit.Mocks.VoidMockMethodCall"; + else + returnType = $"global::TUnit.Mocks.MockMethodCall<{setupReturnType}>"; + + // Build mixed parameter list + var paramParts = new List(); + for (int i = 0; i < method.Parameters.Length; i++) + { + var p = method.Parameters[i]; + if (p.Direction == ParameterDirection.Out) continue; + + if (funcIndices.Contains(i)) + { + paramParts.Add($"global::System.Func<{p.FullyQualifiedType}, bool> {p.Name}"); + } + else if (p.IsRefStruct) + { + if (includeRefStructArgs) + paramParts.Add($"global::TUnit.Mocks.Arguments.RefStructArg<{p.FullyQualifiedType}> {p.Name}"); + } + else + { + paramParts.Add($"global::TUnit.Mocks.Arguments.Arg<{p.FullyQualifiedType}> {p.Name}"); + } + } + + var paramList = string.Join(", ", paramParts); + var typeParams = GetTypeParameterList(method); + var constraints = GetConstraintClauses(method); + + var safeMemberName = GetSafeMemberName(method.Name); + var extensionParam = $"this global::TUnit.Mocks.Mock<{model.FullyQualifiedName}> mock"; + var fullParamList = string.IsNullOrEmpty(paramList) ? extensionParam : $"{extensionParam}, {paramList}"; + + using (writer.Block($"public static {returnType} {safeMemberName}{typeParams}({fullParamList}){constraints}")) + { + // Convert Func params to Arg via implicit conversion + foreach (var idx in funcIndices.OrderBy(i => i)) + { + var p = method.Parameters[idx]; + writer.AppendLine($"global::TUnit.Mocks.Arguments.Arg<{p.FullyQualifiedType}> __fa_{p.Name} = {p.Name};"); + } + + // Build matchers array + var matcherExprs = new List(); + for (int i = 0; i < method.Parameters.Length; i++) + { + var p = method.Parameters[i]; + if (p.Direction == ParameterDirection.Out) continue; + if (!includeRefStructArgs && p.IsRefStruct) continue; + + matcherExprs.Add(funcIndices.Contains(i) ? $"__fa_{p.Name}.Matcher" : $"{p.Name}.Matcher"); + } + + if (matcherExprs.Count == 0) + { + writer.AppendLine("var matchers = global::System.Array.Empty();"); + } + else + { + writer.AppendLine($"var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] {{ {string.Join(", ", matcherExprs)} }};"); + } + + // Return statement + if (useTypedWrapper) + { + var wrapperName = GetWrapperName(safeName, method); + writer.AppendLine($"return new {wrapperName}(global::TUnit.Mocks.Mock.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);"); + } + else if (method.IsVoid || method.IsRefStructReturn) + { + writer.AppendLine($"return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);"); + } + else + { + writer.AppendLine($"return new global::TUnit.Mocks.MockMethodCall<{setupReturnType}>(global::TUnit.Mocks.Mock.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);"); + } + } + } + private static void GeneratePropertyExtensionBlock(CodeWriter writer, List props, MockTypeModel model, string safeName) { using (writer.Block($"extension(global::TUnit.Mocks.Mock<{model.FullyQualifiedName}> mock)")) diff --git a/TUnit.Mocks.Tests/FuncOverloadTests.cs b/TUnit.Mocks.Tests/FuncOverloadTests.cs new file mode 100644 index 0000000000..9cbdf3d664 --- /dev/null +++ b/TUnit.Mocks.Tests/FuncOverloadTests.cs @@ -0,0 +1,201 @@ +using TUnit.Mocks; +using TUnit.Mocks.Arguments; + +namespace TUnit.Mocks.Tests; + +/// +/// Tests for Func<T, bool> parameter overloads that enable inline lambda syntax. +/// Verifies that mock.Method(x => predicate, Any()) compiles and works correctly. +/// +public class FuncOverloadTests +{ + [Test] + public async Task Single_Param_Lambda_Setup_And_Match() + { + // Arrange + var mock = Mock.Of(); + mock.Greet(s => s.StartsWith("Hi")).Returns("matched"); + + // Act + var result = mock.Object.Greet("Hi there"); + + // Assert + await Assert.That(result).IsEqualTo("matched"); + } + + [Test] + public async Task Single_Param_Lambda_No_Match_Returns_Default() + { + // Arrange + var mock = Mock.Of(); + mock.Greet(s => s.StartsWith("Hi")).Returns("matched"); + + // Act + var result = mock.Object.Greet("Hello"); + + // Assert — doesn't match predicate, returns smart default (empty string for non-nullable string) + await Assert.That(result).IsEqualTo(""); + } + + [Test] + public async Task Mixed_Lambda_And_Any() + { + // Arrange + var mock = Mock.Of(); + mock.Add(x => x > 5, Any()).Returns(100); + + // Act + var result = mock.Object.Add(10, 999); + + // Assert + await Assert.That(result).IsEqualTo(100); + } + + [Test] + public async Task Mixed_Lambda_And_Any_No_Match() + { + // Arrange + var mock = Mock.Of(); + mock.Add(x => x > 5, Any()).Returns(100); + + // Act + var result = mock.Object.Add(3, 999); + + // Assert — first param doesn't match predicate + await Assert.That(result).IsEqualTo(0); + } + + [Test] + public async Task Mixed_Any_And_Lambda() + { + // Arrange + var mock = Mock.Of(); + mock.Add(Any(), y => y % 2 == 0).Returns(42); + + // Act + var even = mock.Object.Add(1, 4); + var odd = mock.Object.Add(1, 3); + + // Assert + await Assert.That(even).IsEqualTo(42); + await Assert.That(odd).IsEqualTo(0); + } + + [Test] + public async Task Both_Params_Lambda() + { + // Arrange + var mock = Mock.Of(); + mock.Add(x => x > 0, y => y > 0).Returns(99); + + // Act + var bothPositive = mock.Object.Add(1, 2); + var oneNegative = mock.Object.Add(-1, 2); + + // Assert + await Assert.That(bothPositive).IsEqualTo(99); + await Assert.That(oneNegative).IsEqualTo(0); + } + + [Test] + public async Task Mixed_Lambda_And_Value() + { + // Arrange + var mock = Mock.Of(); + mock.Add(x => x > 5, 3).Returns(50); + + // Act + var matchBoth = mock.Object.Add(10, 3); + var wrongSecond = mock.Object.Add(10, 4); + + // Assert + await Assert.That(matchBoth).IsEqualTo(50); + await Assert.That(wrongSecond).IsEqualTo(0); + } + + [Test] + public async Task Lambda_Verification_WasCalled() + { + // Arrange + var mock = Mock.Of(); + + // Act + mock.Object.Add(10, 20); + + // Assert — verify with lambda predicates + mock.Add(x => x > 0, Any()).WasCalled(); + } + + [Test] + public async Task Lambda_Verification_WasNeverCalled() + { + // Arrange + var mock = Mock.Of(); + + // Act + mock.Object.Add(10, 20); + + // Assert — verify negative case + mock.Add(x => x > 100, Any()).WasNeverCalled(); + } + + [Test] + public async Task Void_Method_Lambda() + { + // Arrange + var mock = Mock.Of(); + var logged = false; + mock.Log(s => s.Contains("error")).Callback(() => logged = true); + + // Act + mock.Object.Log("an error occurred"); + + // Assert + await Assert.That(logged).IsTrue(); + } + + [Test] + public async Task Async_Method_Lambda() + { + // Arrange + var mock = Mock.Of(); + mock.GetNameAsync(s => s.Length > 3).Returns("found"); + + // Act + var result = await mock.Object.GetNameAsync("hello"); + + // Assert + await Assert.That(result).IsEqualTo("found"); + } + + [Test] + public async Task String_Predicate_Lambda() + { + // Arrange + var mock = Mock.Of(); + mock.Greet(name => name.Contains("World")).Returns("Hello World!"); + + // Act + var matched = mock.Object.Greet("World"); + var unmatched = mock.Object.Greet("Bob"); + + // Assert + await Assert.That(matched).IsEqualTo("Hello World!"); + await Assert.That(unmatched).IsEqualTo(""); + } + + [Test] + public async Task Multiple_Lambda_Setups_Last_Wins() + { + // Arrange + var mock = Mock.Of(); + mock.Add(x => x > 0, Any()).Returns(50); + mock.Add(x => x > 10, Any()).Returns(100); + + // Act — 15 matches both, but last-registered wins + var result = mock.Object.Add(15, 0); + + // Assert + await Assert.That(result).IsEqualTo(100); + } +} diff --git a/docs/docs/guides/cookbook.md b/docs/docs/guides/cookbook.md index 6310fc0e93..53e0bd1488 100644 --- a/docs/docs/guides/cookbook.md +++ b/docs/docs/guides/cookbook.md @@ -232,22 +232,23 @@ public class AuthenticatedApiTests ## Mocking Patterns -### Using Moq +### Using TUnit.Mocks (Recommended) + +TUnit.Mocks is TUnit's first-party, source-generated mocking framework. It's **AOT-compatible**, requires no runtime proxy generation, and provides a concise API with no `Arg.` prefix needed: ```csharp -using Moq; -using TUnit.Core; +using TUnit.Mocks; -public class OrderServiceMoqTests +public class OrderServiceTUnitMocksTests { [Test] public async Task ProcessOrder_CallsPaymentService() { - // Arrange - var mockPaymentService = new Mock(); + // Arrange — create mock, configure with concise syntax + var mockPaymentService = Mock.Of(); mockPaymentService - .Setup(p => p.ProcessPaymentAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new PaymentResult { Success = true, TransactionId = "TX123" }); + .ProcessPaymentAsync(Any(), Any()) + .Returns(new PaymentResult { Success = true, TransactionId = "TX123" }); var orderService = new OrderService(mockPaymentService.Object); var order = new Order { Total = 100.00m, PaymentMethod = "Credit Card" }; @@ -257,109 +258,101 @@ public class OrderServiceMoqTests // Assert await Assert.That(result.IsSuccess).IsTrue(); - mockPaymentService.Verify( - p => p.ProcessPaymentAsync(100.00m, "Credit Card"), - Times.Once); + mockPaymentService + .ProcessPaymentAsync(100.00m, "Credit Card") + .WasCalled(Times.Once); } [Test] public async Task ProcessOrder_HandlesPaymentFailure() { // Arrange - var mockPaymentService = new Mock(); + var mockPaymentService = Mock.Of(); mockPaymentService - .Setup(p => p.ProcessPaymentAsync(It.IsAny(), It.IsAny())) - .ThrowsAsync(new PaymentException("Insufficient funds")); + .ProcessPaymentAsync(Any(), Any()) + .Throws(); var orderService = new OrderService(mockPaymentService.Object); var order = new Order { Total = 1000.00m, PaymentMethod = "Credit Card" }; // Act & Assert await Assert.That(async () => await orderService.ProcessAsync(order)) - .ThrowsExactly() - .WithMessage("Insufficient funds"); + .ThrowsExactly(); } -} -``` -### Using NSubstitute - -```csharp -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using TUnit.Core; - -public class OrderServiceNSubstituteTests -{ [Test] - public async Task ProcessOrder_CallsPaymentService() + public async Task ProcessOrder_UsesInlineLambdaPredicates() { - // Arrange - var paymentService = Substitute.For(); - paymentService - .ProcessPaymentAsync(Arg.Any(), Arg.Any()) - .Returns(new PaymentResult { Success = true, TransactionId = "TX123" }); + // Arrange — inline lambdas work directly as argument matchers + var mockPaymentService = Mock.Of(); + mockPaymentService + .ProcessPaymentAsync(amount => amount > 0, method => method.Contains("Card")) + .Returns(new PaymentResult { Success = true, TransactionId = "TX456" }); - var orderService = new OrderService(paymentService); - var order = new Order { Total = 100.00m, PaymentMethod = "Credit Card" }; + var orderService = new OrderService(mockPaymentService.Object); - // Act - var result = await orderService.ProcessAsync(order); + // Act — "Credit Card" contains "Card", so it matches + var result = await orderService.ProcessAsync( + new Order { Total = 50.00m, PaymentMethod = "Credit Card" }); // Assert await Assert.That(result.IsSuccess).IsTrue(); - await paymentService.Received(1).ProcessPaymentAsync(100.00m, "Credit Card"); } [Test] - public async Task ProcessOrder_HandlesPaymentFailure() + public async Task ProcessOrder_CapturesArguments() { - // Arrange - var paymentService = Substitute.For(); - paymentService - .ProcessPaymentAsync(Arg.Any(), Arg.Any()) - .Throws(new PaymentException("Insufficient funds")); + // Arrange — capture arguments for inspection + var amountArg = Any(); + var mockPaymentService = Mock.Of(); + mockPaymentService + .ProcessPaymentAsync(amountArg, Any()) + .Returns(new PaymentResult { Success = true, TransactionId = "TX789" }); - var orderService = new OrderService(paymentService); - var order = new Order { Total = 1000.00m, PaymentMethod = "Credit Card" }; + var orderService = new OrderService(mockPaymentService.Object); + await orderService.ProcessAsync(new Order { Total = 100.00m, PaymentMethod = "Card" }); + await orderService.ProcessAsync(new Order { Total = 200.00m, PaymentMethod = "Card" }); - // Act & Assert - await Assert.That(async () => await orderService.ProcessAsync(order)) - .ThrowsExactly() - .WithMessage("Insufficient funds"); + // Assert — inspect captured values + await Assert.That(amountArg.Values).HasCount().EqualTo(2); + await Assert.That(amountArg.Values[0]).IsEqualTo(100.00m); + await Assert.That(amountArg.Values[1]).IsEqualTo(200.00m); } } ``` +:::tip Why TUnit.Mocks? +- **No `Arg.` prefix** — `Any()`, `Is()`, and matchers work directly thanks to `global using static` +- **Inline lambdas** — write predicates directly in the call: `mock.Add(x => x > 5, Any())` — works for all types +- **Implicit values** — pass raw values (`42`, `"hello"`) directly as exact matchers +- **AOT-compatible** — source-generated mocks work with Native AOT, trimming, and single-file publishing +- **Built-in capture** — every `Arg` automatically captures values for inspection via `.Values` and `.Latest` + +See the full [TUnit.Mocks documentation](../test-authoring/mocking) for setup, verification, argument matchers, and more. +::: + ### Partial Mocks and Spy Pattern ```csharp -using Moq; +using TUnit.Mocks; public class NotificationServiceTests { [Test] public async Task SendNotification_LogsAttempt() { - // Arrange - create a partial mock that calls real methods - var mockLogger = new Mock(); - var notificationService = new Mock(mockLogger.Object) - { - CallBase = true // Call real implementation - }; + // Arrange — partial mock calls base implementation for unconfigured methods + var mockLogger = Mock.Of(); + var notificationService = Mock.OfPartial(mockLogger.Object); // Override only the SendEmail method - notificationService - .Setup(n => n.SendEmailAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(true); + notificationService.SendEmailAsync(Any(), Any()).Returns(true); // Act await notificationService.Object.NotifyUserAsync("user@example.com", "Hello"); - // Assert - verify the real method called SendEmail - notificationService.Verify( - n => n.SendEmailAsync("user@example.com", It.IsAny()), - Times.Once); + // Assert — verify the real method called SendEmail + notificationService.SendEmailAsync("user@example.com", Any()).WasCalled(Times.Once); } } ``` @@ -795,7 +788,7 @@ These cookbook recipes cover the most common testing scenarios. You can adapt th - **Dependency Injection**: Use service collections for realistic testing with dependencies - **API Testing**: Use `WebApplicationFactory` for end-to-end API tests -- **Mocking**: Choose Moq or NSubstitute based on your preference +- **Mocking**: Use TUnit.Mocks for AOT-compatible, source-generated mocking with a concise API - **Data-Driven Tests**: Use `MethodDataSource` or `DataSourceGenerator` for parameterized tests - **Exception Testing**: Use TUnit's fluent exception assertions - **Integration Tests**: Test with real databases, containers, or file systems diff --git a/docs/docs/test-authoring/mocking/argument-matchers.md b/docs/docs/test-authoring/mocking/argument-matchers.md index 84c39b80b3..8af608f407 100644 --- a/docs/docs/test-authoring/mocking/argument-matchers.md +++ b/docs/docs/test-authoring/mocking/argument-matchers.md @@ -6,25 +6,32 @@ sidebar_position: 4 Argument matchers control which calls a setup or verification matches. The same matchers work in both contexts — the chain method determines whether it's a setup or verification. +:::tip Static Import +TUnit.Mocks automatically imports the `Arg` class via `global using static`, so you can call matcher methods directly — no `Arg.` prefix needed. +::: + ## Quick Reference | Matcher | Matches | |---|---| -| `Arg.Any()` | Any value of type T (including null) | -| `Arg.Is(value)` | Exact equality | -| `Arg.Is(predicate)` | Values satisfying a predicate | -| `Arg.IsNull()` | Null only | -| `Arg.IsNotNull()` | Any non-null value | -| `Arg.Matches(pattern)` | String matching a regex pattern | -| `Arg.Matches(regex)` | String matching a compiled `Regex` | -| `Arg.Contains(item)` | Collection containing an element | -| `Arg.HasCount(n)` | Collection with exactly n elements | -| `Arg.IsEmpty()` | Empty collection | -| `Arg.SequenceEquals(expected)` | Collection matching element-by-element | -| `Arg.IsInRange(min, max)` | Value within an inclusive range | -| `Arg.IsIn(values)` | Value in a set | -| `Arg.IsNotIn(values)` | Value not in a set | -| `Arg.Not(inner)` | Negation of another matcher | +| `Any()` / `Any()` | Any value of type T (including null) | +| `Is(value)` | Exact equality | +| `Is(predicate)` | Values satisfying a predicate | +| Raw value (e.g. `42`, `"hello"`) | Exact equality (implicit conversion) | +| Inline lambda (e.g. `x => x > 5`) | Predicate matching (all types) | +| `Func` variable | Predicate matching (implicit conversion) | +| `IsNull()` | Null only | +| `IsNotNull()` | Any non-null value | +| `Matches(pattern)` | String matching a regex pattern | +| `Matches(regex)` | String matching a compiled `Regex` | +| `Contains(item)` | Collection containing an element | +| `HasCount(n)` | Collection with exactly n elements | +| `IsEmpty()` | Empty collection | +| `SequenceEquals(expected)` | Collection matching element-by-element | +| `IsInRange(min, max)` | Value within an inclusive range | +| `IsIn(values)` | Value in a set | +| `IsNotIn(values)` | Value not in a set | +| `Not(inner)` | Negation of another matcher | | `RefStructArg.Any` | Any value of a ref struct type (.NET 9+) | ## Basic Matchers @@ -34,7 +41,7 @@ Argument matchers control which calls a setup or verification matches. The same Matches any value, including null: ```csharp -mock.GetUser(Arg.Any()).Returns(new User("Default")); +mock.GetUser(Any()).Returns(new User("Default")); svc.GetUser(1); // matches svc.GetUser(999); // matches @@ -45,8 +52,8 @@ svc.GetUser(999); // matches Match a specific value using equality: ```csharp -mock.GetUser(Arg.Is(42)).Returns(new User("Alice")); -mock.GetUser(Arg.Is(99)).Returns(new User("Bob")); +mock.GetUser(Is(42)).Returns(new User("Alice")); +mock.GetUser(Is(99)).Returns(new User("Bob")); svc.GetUser(42); // returns Alice svc.GetUser(99); // returns Bob @@ -54,9 +61,9 @@ svc.GetUser(1); // no match — returns default ``` :::tip Implicit Conversion -Raw values are implicitly converted to `Arg.Is(value)`, so these are equivalent: +Raw values are implicitly converted to exact matchers, so these are equivalent: ```csharp -mock.GetUser(Arg.Is(42)).Returns(new User("Alice")); +mock.GetUser(Is(42)).Returns(new User("Alice")); mock.GetUser(42).Returns(new User("Alice")); // same thing ``` ::: @@ -66,15 +73,45 @@ mock.GetUser(42).Returns(new User("Alice")); // same thing Match values satisfying a condition: ```csharp -mock.GetUser(Arg.Is(id => id > 0)).Returns(new User("Valid")); -mock.GetUser(Arg.Is(id => id <= 0)).Throws(); +mock.GetUser(Is(id => id > 0)).Returns(new User("Valid")); +mock.GetUser(Is(id => id <= 0)).Throws(); +``` + +### Inline Lambda Predicates + +You can write lambda predicates directly in mock setup and verification calls — no `Is()` wrapper needed: + +```csharp +// Inline lambda — works for both value types and reference types +mock.GetUser(id => id > 0).Returns(validUser); +mock.Greet(name => name.StartsWith("A")).Returns("A-name"); + +// Mix lambdas with Any() or raw values +mock.Add(x => x > 5, Any()).Returns(100); +mock.Search(name => name.Length > 3, 10).Returns(results); + +// Both parameters as lambdas +mock.Add(x => x > 0, y => y % 2 == 0).Returns(42); +``` + +:::tip +Inline lambdas are the recommended syntax. They work with all parameter types — value types (`int`, `bool`, etc.) and reference types (`string`, `object`, etc.) — thanks to source-generated `Func` overloads. +::: + +You can also assign predicates to `Func` variables for reuse: + +```csharp +Func isEmail = s => s != null && s.Contains("@"); +Func isLong = s => s != null && s.Length > 10; + +mock.SendEmail(isEmail, "Welcome", isLong).Returns(true); ``` ### Null and NotNull ```csharp -mock.Process(Arg.IsNull()).Returns("was null"); -mock.Process(Arg.IsNotNull()).Returns("had value"); +mock.Process(IsNull()).Returns("was null"); +mock.Process(IsNotNull()).Returns("had value"); ``` ## String Matchers @@ -83,7 +120,7 @@ mock.Process(Arg.IsNotNull()).Returns("had value"); ```csharp // Match strings against a regex pattern -mock.Search(Arg.Matches(@"^user_\d+$")).Returns(new[] { "found" }); +mock.Search(Matches(@"^user_\d+$")).Returns(new[] { "found" }); svc.Search("user_42"); // matches svc.Search("admin_1"); // no match @@ -93,7 +130,7 @@ svc.Search("admin_1"); // no match ```csharp var pattern = new Regex(@"^user_\d+$", RegexOptions.Compiled); -mock.Search(Arg.Matches(pattern)).Returns(new[] { "found" }); +mock.Search(Matches(pattern)).Returns(new[] { "found" }); ``` ## Collection Matchers @@ -101,7 +138,7 @@ mock.Search(Arg.Matches(pattern)).Returns(new[] { "found" }); ### Contains ```csharp -mock.ProcessItems(Arg.Contains, int>(42)).Returns(true); +mock.ProcessItems(Contains, int>(42)).Returns(true); svc.ProcessItems(new List { 1, 42, 3 }); // matches — contains 42 svc.ProcessItems(new List { 1, 2, 3 }); // no match @@ -110,7 +147,7 @@ svc.ProcessItems(new List { 1, 2, 3 }); // no match ### HasCount ```csharp -mock.ProcessItems(Arg.HasCount>(3)).Returns(true); +mock.ProcessItems(HasCount>(3)).Returns(true); svc.ProcessItems(new List { 1, 2, 3 }); // matches — count is 3 svc.ProcessItems(new List { 1, 2 }); // no match @@ -119,7 +156,7 @@ svc.ProcessItems(new List { 1, 2 }); // no match ### IsEmpty ```csharp -mock.ProcessItems(Arg.IsEmpty>()).Returns(false); +mock.ProcessItems(IsEmpty>()).Returns(false); svc.ProcessItems(new List()); // matches svc.ProcessItems(new List { 1 }); // no match @@ -129,7 +166,7 @@ svc.ProcessItems(new List { 1 }); // no match ```csharp mock.ProcessItems( - Arg.SequenceEquals, int>(new[] { 1, 2, 3 }) + SequenceEquals, int>(new[] { 1, 2, 3 }) ).Returns(true); svc.ProcessItems(new List { 1, 2, 3 }); // matches @@ -141,7 +178,7 @@ svc.ProcessItems(new List { 3, 2, 1 }); // no match — wrong order ### IsInRange ```csharp -mock.SetAge(Arg.IsInRange(0, 120)).Returns(true); +mock.SetAge(IsInRange(0, 120)).Returns(true); svc.SetAge(25); // matches svc.SetAge(-1); // no match @@ -151,8 +188,8 @@ svc.SetAge(200); // no match ### IsIn / IsNotIn ```csharp -mock.GetRole(Arg.IsIn("admin", "editor", "viewer")).Returns(true); -mock.GetRole(Arg.IsNotIn("admin", "superadmin")).Returns(false); +mock.GetRole(IsIn("admin", "editor", "viewer")).Returns(true); +mock.GetRole(IsNotIn("admin", "superadmin")).Returns(false); ``` ## Negation @@ -160,7 +197,7 @@ mock.GetRole(Arg.IsNotIn("admin", "superadmin")).Returns(false); Wrap any matcher with `Not` to invert it: ```csharp -mock.Process(Arg.Not(Arg.Is(0))).Returns("non-zero"); +mock.Process(Not(Is(0))).Returns("non-zero"); // Matches any int except 0 ``` @@ -169,7 +206,7 @@ mock.Process(Arg.Not(Arg.Is(0))).Returns("non-zero"); Every `Arg` matcher automatically captures the values it sees: ```csharp -var nameArg = Arg.Any(); +var nameArg = Any(); mock.Greet(nameArg).Returns("Hi"); svc.Greet("Alice"); @@ -268,7 +305,7 @@ public class StringLengthMatcher : IArgumentMatcher } // Usage -mock.Greet(Arg.Matches(new StringLengthMatcher(3, 50))) +mock.Greet(Matches(new StringLengthMatcher(3, 50))) .Returns("Valid name"); ``` diff --git a/docs/docs/test-authoring/mocking/index.md b/docs/docs/test-authoring/mocking/index.md index 8b04b71633..6f8c5ba284 100644 --- a/docs/docs/test-authoring/mocking/index.md +++ b/docs/docs/test-authoring/mocking/index.md @@ -48,7 +48,7 @@ public class GreeterTests var mock = Mock.Of(); // Configure — set up a return value - mock.Greet(Arg.Any()).Returns("Hello!"); + mock.Greet(Any()).Returns("Hello!"); // Act — use the mock object IGreeter greeter = mock.Object; @@ -91,7 +91,7 @@ var strict = Mock.Of(MockBehavior.Strict); // throws on unconfigured ```csharp var mock = Mock.Of(); -mock.GetUser(Arg.Any()).Returns(user); // setup — .Returns() makes it a stub +mock.GetUser(Any()).Returns(user); // setup — .Returns() makes it a stub mock.GetUser(42).WasCalled(Times.Once); // verify — .WasCalled() makes it a check mock.RaiseOnMessage("hi"); // raise events — Raise{EventName}() mock.Object // the T instance to pass to your code under test @@ -113,6 +113,32 @@ IGreeter greeter = mock; // implicit conversion | `MockBehavior.Loose` | Return smart defaults (`0`, `""`, `false`, `null`, auto-mocked interfaces) | Yes | | `MockBehavior.Strict` | Throw `MockStrictBehaviorException` | No | +### Concise Argument Matching + +TUnit.Mocks imports matchers globally — no `Arg.` prefix needed. Raw values, inline lambdas, and `Any()` work directly as arguments: + +```csharp +var mock = Mock.Of(); + +// Any() — matches everything +mock.GetUser(Any()).Returns(user); + +// Raw values — implicit exact matching +mock.GetUser(42).Returns(alice); + +// Inline lambdas — predicate matching directly in the call +mock.GetUser(id => id > 0).Returns(validUser); +mock.GetByRole(role => role == "admin").Returns(admins); + +// Mix lambdas with Any() or raw values +mock.Search(name => name.StartsWith("A"), Any()).Returns(results); + +// Is() — explicit predicate matching (also works) +mock.GetUser(Is(id => id > 0)).Returns(validUser); +``` + +See [Argument Matchers](argument-matchers) for the full API. + ## What's Next - [Setup & Stubbing](setup) — configure return values, callbacks, exceptions, and property behaviors From 4ad1bcd4e2edee9df7a49c3a9f0a15d9deaf2280 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:20:06 +0000 Subject: [PATCH 2/4] feat(mocks): concise API with untyped Any(), implicit conversions, and global static import Add untyped `Arg.Any()` returning a sentinel that implicitly converts to `Arg` via `implicit operator Arg(Arg)`, enabling `Any()` without type arguments. Add `implicit operator Arg(Func)` for predicate matching. Add `global using static Arg` to TUnit.Mocks.targets so matchers (`Any()`, `Is()`, `IsInRange()`, etc.) can be called without the `Arg.` prefix. Update all tests and docs to use the concise syntax. --- Library.props | 1 + TUnit.Mocks.Tests/AdditionalCoverageTests.cs | 4 +- TUnit.Mocks.Tests/ArgumentMatcherTests.cs | 30 +- TUnit.Mocks.Tests/AsyncTests.cs | 2 +- TUnit.Mocks.Tests/AsyncVerificationTests.cs | 26 +- TUnit.Mocks.Tests/AutoRaiseEventTests.cs | 10 +- TUnit.Mocks.Tests/AutoTrackPropertyTests.cs | 4 +- TUnit.Mocks.Tests/CallbackTests.cs | 12 +- TUnit.Mocks.Tests/CollectionMatcherTests.cs | 20 +- .../ComprehensiveOutRefSpanTests.cs | 70 ++-- TUnit.Mocks.Tests/CustomMatcherTests.cs | 12 +- TUnit.Mocks.Tests/DelegateMockTests.cs | 16 +- TUnit.Mocks.Tests/DiagnosticsTests.cs | 4 +- TUnit.Mocks.Tests/EdgeCaseTests.cs | 64 ++-- TUnit.Mocks.Tests/ErrorMessageTests.cs | 2 +- TUnit.Mocks.Tests/GenericTests.cs | 6 +- .../ImplicitArgConversionTests.cs | 347 ++++++++++++++++++ TUnit.Mocks.Tests/InParameterTests.cs | 24 +- TUnit.Mocks.Tests/InvocationsTests.cs | 8 +- TUnit.Mocks.Tests/MatcherTests.cs | 16 +- TUnit.Mocks.Tests/MockRepositoryTests.cs | 24 +- TUnit.Mocks.Tests/MultipleInterfaceTests.cs | 8 +- TUnit.Mocks.Tests/OutRefAssignmentTests.cs | 8 +- TUnit.Mocks.Tests/OutRefTests.cs | 2 +- TUnit.Mocks.Tests/PartialMockTests.cs | 12 +- TUnit.Mocks.Tests/PropertyTests.cs | 2 +- TUnit.Mocks.Tests/ProtectedMemberTests.cs | 20 +- TUnit.Mocks.Tests/RealWorldScenarioTests.cs | 38 +- TUnit.Mocks.Tests/RefStructTests.cs | 6 +- TUnit.Mocks.Tests/RegexMatcherTests.cs | 10 +- TUnit.Mocks.Tests/ResetTests.cs | 8 +- TUnit.Mocks.Tests/SequentialBehaviorTests.cs | 10 +- TUnit.Mocks.Tests/SpanReturnTests.cs | 6 +- TUnit.Mocks.Tests/ThreadSafetyTests.cs | 14 +- TUnit.Mocks.Tests/TypedCallbackTests.cs | 34 +- TUnit.Mocks.Tests/UntypedAnyTests.cs | 347 ++++++++++++++++++ TUnit.Mocks.Tests/VerifyAllTests.cs | 10 +- TUnit.Mocks.Tests/VerifyNoOtherCallsTests.cs | 8 +- TUnit.Mocks.Tests/WrapRealObjectTests.cs | 16 +- TUnit.Mocks/Arguments/Arg.cs | 8 +- TUnit.Mocks/Arguments/ArgOfT.cs | 10 +- TUnit.Mocks/Arguments/RefStructArg.cs | 2 +- TUnit.Mocks/MockEngine.cs | 18 +- TUnit.Mocks/MockRepository.cs | 2 +- TUnit.Mocks/Setup/MethodSetup.cs | 2 +- TUnit.Mocks/TUnit.Mocks.targets | 1 + docs/docs/assertions/null-and-default.md | 2 +- docs/docs/assertions/types.md | 2 +- docs/docs/guides/best-practices.md | 14 +- docs/docs/test-authoring/mocking/advanced.md | 14 +- docs/docs/test-authoring/mocking/setup.md | 36 +- .../test-authoring/mocking/verification.md | 18 +- docs/docs/troubleshooting.md | 40 +- 53 files changed, 1061 insertions(+), 369 deletions(-) create mode 100644 TUnit.Mocks.Tests/ImplicitArgConversionTests.cs create mode 100644 TUnit.Mocks.Tests/UntypedAnyTests.cs diff --git a/Library.props b/Library.props index a4f9a83316..fb48df06f6 100644 --- a/Library.props +++ b/Library.props @@ -3,6 +3,7 @@ netstandard2.0;net8.0;net9.0;net10.0 true + $(NoWarn);AD0001 false diff --git a/TUnit.Mocks.Tests/AdditionalCoverageTests.cs b/TUnit.Mocks.Tests/AdditionalCoverageTests.cs index 973a1cf87b..54adf1998f 100644 --- a/TUnit.Mocks.Tests/AdditionalCoverageTests.cs +++ b/TUnit.Mocks.Tests/AdditionalCoverageTests.cs @@ -206,7 +206,7 @@ public async Task VerifyAll_Message_Includes_Matcher_Descriptions() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Is(x => x > 0)).Returns(1); + mock.Add(Any(), Is(x => x > 0)).Returns(1); // Act — don't call the method @@ -239,7 +239,7 @@ public async Task VerifyAll_Passes_When_All_Setups_Invoked() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(1); + mock.Add(Any(), Any()).Returns(1); mock.GetName().Returns("name"); // Act — invoke all setups diff --git a/TUnit.Mocks.Tests/ArgumentMatcherTests.cs b/TUnit.Mocks.Tests/ArgumentMatcherTests.cs index 2926a712fc..5c33418cd2 100644 --- a/TUnit.Mocks.Tests/ArgumentMatcherTests.cs +++ b/TUnit.Mocks.Tests/ArgumentMatcherTests.cs @@ -13,7 +13,7 @@ public async Task Arg_Any_Matches_All_Values() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); // Act ICalculator calc = mock.Object; @@ -30,7 +30,7 @@ public async Task Arg_Is_With_Predicate_Matches_When_True() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Is(a => a > 0), Arg.Is(b => b > 0)).Returns(100); + mock.Add(Is(a => a > 0), Is(b => b > 0)).Returns(100); // Act ICalculator calc = mock.Object; @@ -50,7 +50,7 @@ public async Task Arg_Is_With_Exact_Value() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Is(10), Arg.Is(20)).Returns(30); + mock.Add(Is(10), Is(20)).Returns(30); // Act ICalculator calc = mock.Object; @@ -67,9 +67,9 @@ public async Task Arg_Is_With_Exact_Value() public async Task Arg_Capture_Captures_Values() { // Arrange - var firstArg = Arg.Any(); + var firstArg = Any(); var mock = Mock.Of(); - mock.Add(firstArg, Arg.Any()).Returns(1); + mock.Add(firstArg, Any()).Returns(1); // Act ICalculator calc = mock.Object; @@ -91,7 +91,7 @@ public async Task Mixed_Matchers_And_Exact_Values() // Arrange var mock = Mock.Of(); // First arg: any int. Second arg: exact 5. - mock.Add(Arg.Any(), 5).Returns(99); + mock.Add(Any(), 5).Returns(99); // Act ICalculator calc = mock.Object; @@ -111,7 +111,7 @@ public async Task Arg_IsNull_Matches_Null_Values() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.IsNull()).Returns("got null"); + mock.Greet(IsNull()).Returns("got null"); // Act IGreeter greeter = mock.Object; @@ -128,7 +128,7 @@ public async Task Arg_IsNotNull_Matches_NonNull_Values() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.IsNotNull()).Returns("got something"); + mock.Greet(IsNotNull()).Returns("got something"); // Act IGreeter greeter = mock.Object; @@ -146,7 +146,7 @@ public async Task Arg_IsNotNull_Matches_NonNull_Values() public async Task Arg_Capture_With_String_Values() { // Arrange - var nameArg = Arg.Any(); + var nameArg = Any(); var mock = Mock.Of(); mock.Greet(nameArg).Returns("hi"); @@ -168,7 +168,7 @@ public async Task Multiple_Setups_With_Different_Matchers() // Arrange — more specific setup first, then broader var mock = Mock.Of(); mock.Add(1, 1).Returns(100); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); // Act ICalculator calc = mock.Object; @@ -183,7 +183,7 @@ public async Task Specific_Setup_After_Any_Takes_Precedence() { // Arrange — broad setup first, then specific var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); mock.Add(1, 1).Returns(100); // Act @@ -198,7 +198,7 @@ public async Task Specific_Setup_After_Any_Takes_Precedence() public async Task Arg_Capture_Latest_Returns_Default_When_Empty() { // Arrange - var arg = Arg.Any(); + var arg = Any(); // Assert — no calls yet await Assert.That(arg.Values).Count().IsEqualTo(0); @@ -209,9 +209,9 @@ public async Task Arg_Capture_Latest_Returns_Default_When_Empty() public async Task Arg_Capture_Does_Not_Capture_On_Partial_Match() { // Arrange — setup requires first arg = any (captured), second arg starts with "prefix" - var firstArg = Arg.Any(); + var firstArg = Any(); var mock = Mock.Of(); - mock.Add(firstArg, Arg.Is(b => b > 100)).Returns(999); + mock.Add(firstArg, Is(b => b > 100)).Returns(999); ICalculator calc = mock.Object; @@ -235,7 +235,7 @@ public async Task Predicate_Matcher_With_String() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Is(s => s != null && s.StartsWith("A"))).Returns("starts with A"); + mock.Greet(Is(s => s != null && s.StartsWith("A"))).Returns("starts with A"); // Act IGreeter greeter = mock.Object; diff --git a/TUnit.Mocks.Tests/AsyncTests.cs b/TUnit.Mocks.Tests/AsyncTests.cs index a340b1e19c..87393948e1 100644 --- a/TUnit.Mocks.Tests/AsyncTests.cs +++ b/TUnit.Mocks.Tests/AsyncTests.cs @@ -42,7 +42,7 @@ public async Task Task_String_Returns_Unwrapped_Value() { // Arrange var mock = Mock.Of(); - mock.GetNameAsync(Arg.Any()).Returns("hello"); + mock.GetNameAsync(Any()).Returns("hello"); IAsyncService service = mock.Object; diff --git a/TUnit.Mocks.Tests/AsyncVerificationTests.cs b/TUnit.Mocks.Tests/AsyncVerificationTests.cs index 03049533d2..a35a251d36 100644 --- a/TUnit.Mocks.Tests/AsyncVerificationTests.cs +++ b/TUnit.Mocks.Tests/AsyncVerificationTests.cs @@ -9,12 +9,12 @@ public class AsyncVerificationTests public async Task WasCalled_Times_Once_Passes() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; _ = calc.Add(1, 2); - await Assert.That(mock.Add(Arg.Any(), Arg.Any())) + await Assert.That(mock.Add(Any(), Any())) .WasCalled(Times.Once); } @@ -22,14 +22,14 @@ await Assert.That(mock.Add(Arg.Any(), Arg.Any())) public async Task WasCalled_Times_Exactly_Passes() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; _ = calc.Add(1, 2); _ = calc.Add(3, 4); _ = calc.Add(5, 6); - await Assert.That(mock.Add(Arg.Any(), Arg.Any())) + await Assert.That(mock.Add(Any(), Any())) .WasCalled(Times.Exactly(3)); } @@ -37,13 +37,13 @@ await Assert.That(mock.Add(Arg.Any(), Arg.Any())) public async Task WasCalled_Wrong_Count_Fails() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; _ = calc.Add(1, 2); await Assert.ThrowsAsync(async () => - await Assert.That(mock.Add(Arg.Any(), Arg.Any())) + await Assert.That(mock.Add(Any(), Any())) .WasCalled(Times.Exactly(5))); } @@ -52,7 +52,7 @@ public async Task WasNeverCalled_Passes_When_Not_Called() { var mock = Mock.Of(); - await Assert.That(mock.Add(Arg.Any(), Arg.Any())) + await Assert.That(mock.Add(Any(), Any())) .WasNeverCalled(); } @@ -60,13 +60,13 @@ await Assert.That(mock.Add(Arg.Any(), Arg.Any())) public async Task WasNeverCalled_Fails_When_Called() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; _ = calc.Add(1, 2); await Assert.ThrowsAsync(async () => - await Assert.That(mock.Add(Arg.Any(), Arg.Any())) + await Assert.That(mock.Add(Any(), Any())) .WasNeverCalled()); } @@ -74,13 +74,13 @@ await Assert.That(mock.Add(Arg.Any(), Arg.Any())) public async Task WasCalled_AtLeastOnce_Passes() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; _ = calc.Add(1, 2); _ = calc.Add(3, 4); - await Assert.That(mock.Add(Arg.Any(), Arg.Any())) + await Assert.That(mock.Add(Any(), Any())) .WasCalled(Times.AtLeastOnce); } @@ -123,7 +123,7 @@ await Assert.That(mock.Name) public async Task Multiple_Verifications_In_Sequence() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); mock.GetName().Returns("test"); ICalculator calc = mock.Object; @@ -131,7 +131,7 @@ public async Task Multiple_Verifications_In_Sequence() _ = calc.GetName(); _ = calc.GetName(); - await Assert.That(mock.Add(Arg.Any(), Arg.Any())) + await Assert.That(mock.Add(Any(), Any())) .WasCalled(Times.Once); await Assert.That(mock.GetName()) diff --git a/TUnit.Mocks.Tests/AutoRaiseEventTests.cs b/TUnit.Mocks.Tests/AutoRaiseEventTests.cs index 6c02ebfbe4..8abfb7f882 100644 --- a/TUnit.Mocks.Tests/AutoRaiseEventTests.cs +++ b/TUnit.Mocks.Tests/AutoRaiseEventTests.cs @@ -25,7 +25,7 @@ public async Task Raises_Event_When_Method_Returns_Value() mock.Object.StatusChanged += (sender, status) => receivedStatus = status; - mock.Process(Arg.Any()) + mock.Process(Any()) .Returns(true) .RaisesStatusChanged("completed"); @@ -42,7 +42,7 @@ public async Task Raises_Event_When_Void_Method_Called() mock.Object.StatusChanged += (sender, status) => receivedStatus = status; - mock.Execute(Arg.Any()) + mock.Execute(Any()) .RaisesStatusChanged("executed"); mock.Object.Execute("run"); @@ -58,7 +58,7 @@ public async Task Multiple_Raises_Fire_In_Order() mock.Object.StatusChanged += (sender, status) => receivedStatuses.Add(status); - mock.Process(Arg.Any()) + mock.Process(Any()) .Returns(true) .RaisesStatusChanged("first") .RaisesStatusChanged("second"); @@ -75,7 +75,7 @@ public async Task Raises_With_No_Subscribers_Does_Not_Throw() { var mock = Mock.Of(); - mock.Process(Arg.Any()) + mock.Process(Any()) .Returns(true) .RaisesStatusChanged("ignored"); @@ -93,7 +93,7 @@ public async Task Raises_On_Each_Call() mock.Object.StatusChanged += (sender, status) => callCount++; - mock.Process(Arg.Any()) + mock.Process(Any()) .Returns(true) .RaisesStatusChanged("ping"); diff --git a/TUnit.Mocks.Tests/AutoTrackPropertyTests.cs b/TUnit.Mocks.Tests/AutoTrackPropertyTests.cs index d2be1da16a..dcdc370a11 100644 --- a/TUnit.Mocks.Tests/AutoTrackPropertyTests.cs +++ b/TUnit.Mocks.Tests/AutoTrackPropertyTests.cs @@ -125,7 +125,7 @@ public async Task Strict_Mode_Does_Not_AutoTrack_By_Default() { // Arrange — strict mode requires explicit SetupAllProperties var mock = Mock.Of(MockBehavior.Strict); - mock.Name.Set(Arg.Any()); + mock.Name.Set(Any()); mock.Name.Returns(""); // Act @@ -141,7 +141,7 @@ public async Task Strict_Mode_With_SetupAllProperties_Tracks() // Arrange — strict mode with explicit opt-in var mock = Mock.Of(MockBehavior.Strict); Mock.SetupAllProperties(mock); - mock.Name.Set(Arg.Any()); + mock.Name.Set(Any()); mock.Name.Returns(""); // Act diff --git a/TUnit.Mocks.Tests/CallbackTests.cs b/TUnit.Mocks.Tests/CallbackTests.cs index 8b109ebf69..9c97005c45 100644 --- a/TUnit.Mocks.Tests/CallbackTests.cs +++ b/TUnit.Mocks.Tests/CallbackTests.cs @@ -14,7 +14,7 @@ public async Task Callback_Is_Invoked_When_Method_Is_Called() // Arrange var callbackInvoked = false; var mock = Mock.Of(); - mock.Log(Arg.Any()) + mock.Log(Any()) .Callback(() => callbackInvoked = true); ICalculator calc = mock.Object; @@ -32,7 +32,7 @@ public async Task Callback_With_Returns_Both_Execute() // Arrange var callbackInvoked = false; var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Callback(() => callbackInvoked = true) .Then() .Returns(42); @@ -55,7 +55,7 @@ public async Task Multiple_Callbacks_Via_Chaining() // Arrange var callCount = 0; var mock = Mock.Of(); - mock.Log(Arg.Any()) + mock.Log(Any()) .Callback(() => callCount++) .Then() .Callback(() => callCount += 10); @@ -76,7 +76,7 @@ public async Task Callback_Invoked_For_Each_Call() // Arrange var callCount = 0; var mock = Mock.Of(); - mock.Log(Arg.Any()) + mock.Log(Any()) .Callback(() => callCount++); ICalculator calc = mock.Object; @@ -96,7 +96,7 @@ public async Task Callback_On_Method_With_Return_Value() // Arrange var lastArgs = ""; var mock = Mock.Of(); - mock.Greet(Arg.Any()) + mock.Greet(Any()) .Callback(() => lastArgs = "called") .Then() .Returns("hello"); @@ -120,7 +120,7 @@ public async Task Computed_Return_Via_Factory() // Arrange var counter = 0; var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Returns(() => ++counter); ICalculator calc = mock.Object; diff --git a/TUnit.Mocks.Tests/CollectionMatcherTests.cs b/TUnit.Mocks.Tests/CollectionMatcherTests.cs index 0760d8fb74..8b790d73da 100644 --- a/TUnit.Mocks.Tests/CollectionMatcherTests.cs +++ b/TUnit.Mocks.Tests/CollectionMatcherTests.cs @@ -21,7 +21,7 @@ public async Task Arg_Contains_Matches_List_With_Item() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.Contains, int>(42)).Returns(1); + mock.ProcessItems(Contains, int>(42)).Returns(1); // Act var svc = mock.Object; @@ -36,7 +36,7 @@ public async Task Arg_Contains_Does_Not_Match_Without_Item() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.Contains, int>(42)).Returns(1); + mock.ProcessItems(Contains, int>(42)).Returns(1); // Act var svc = mock.Object; @@ -51,7 +51,7 @@ public async Task Arg_HasCount_Matches_List_With_Exact_Count() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.HasCount>(3)).Returns(99); + mock.ProcessItems(HasCount>(3)).Returns(99); // Act var svc = mock.Object; @@ -66,7 +66,7 @@ public async Task Arg_HasCount_Does_Not_Match_Wrong_Count() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.HasCount>(3)).Returns(99); + mock.ProcessItems(HasCount>(3)).Returns(99); // Act var svc = mock.Object; @@ -81,7 +81,7 @@ public async Task Arg_IsEmpty_Matches_Empty_List() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.IsEmpty>()).Returns(77); + mock.ProcessItems(IsEmpty>()).Returns(77); // Act var svc = mock.Object; @@ -95,7 +95,7 @@ public async Task Arg_IsEmpty_Does_Not_Match_NonEmpty() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.IsEmpty>()).Returns(77); + mock.ProcessItems(IsEmpty>()).Returns(77); // Act var svc = mock.Object; @@ -109,7 +109,7 @@ public async Task Arg_SequenceEquals_Matches_Exact_Sequence() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.SequenceEquals, int>(new[] { 1, 2, 3 })).Returns(55); + mock.ProcessItems(SequenceEquals, int>(new[] { 1, 2, 3 })).Returns(55); // Act var svc = mock.Object; @@ -123,7 +123,7 @@ public async Task Arg_SequenceEquals_Does_Not_Match_Different_Sequence() { // Arrange var mock = Mock.Of(); - mock.ProcessItems(Arg.SequenceEquals, int>(new[] { 1, 2, 3 })).Returns(55); + mock.ProcessItems(SequenceEquals, int>(new[] { 1, 2, 3 })).Returns(55); // Act var svc = mock.Object; @@ -139,7 +139,7 @@ public async Task Arg_SequenceEquals_With_Strings() { // Arrange var mock = Mock.Of(); - mock.JoinNames(Arg.SequenceEquals, string>(new[] { "a", "b" })).Returns("matched"); + mock.JoinNames(SequenceEquals, string>(new[] { "a", "b" })).Returns("matched"); // Act var svc = mock.Object; @@ -155,7 +155,7 @@ public async Task Arg_Contains_With_Strings() { // Arrange var mock = Mock.Of(); - mock.JoinNames(Arg.Contains, string>("hello")).Returns("found"); + mock.JoinNames(Contains, string>("hello")).Returns("found"); // Act var svc = mock.Object; diff --git a/TUnit.Mocks.Tests/ComprehensiveOutRefSpanTests.cs b/TUnit.Mocks.Tests/ComprehensiveOutRefSpanTests.cs index 1c8f2bb0f3..f1f8a37af5 100644 --- a/TUnit.Mocks.Tests/ComprehensiveOutRefSpanTests.cs +++ b/TUnit.Mocks.Tests/ComprehensiveOutRefSpanTests.cs @@ -232,7 +232,7 @@ public async Task Out_ReadOnlySpan_Char_Mixed_Params_With_Matchers() public async Task Out_ReadOnlySpan_Char_Arg_Any_Matcher() { var mock = Mock.Of(); - mock.GetToken(Arg.Any()) + mock.GetToken(Any()) .Returns(1) .SetsOutToken("x".AsSpan()); @@ -251,7 +251,7 @@ public async Task Out_ReadOnlySpan_Char_Arg_Any_Matcher() public async Task Out_ReadOnlySpan_Char_Arg_Is_Predicate() { var mock = Mock.Of(); - mock.GetToken(Arg.Is(s => s.StartsWith("J"))) + mock.GetToken(Is(s => s.StartsWith("J"))) .Returns(42) .SetsOutToken("json".AsSpan()); @@ -271,7 +271,7 @@ public async Task Out_ReadOnlySpan_Char_Arg_Is_Predicate() public async Task Out_ReadOnlySpan_Char_Verification_Multiple() { var mock = Mock.Of(); - mock.GetToken(Arg.Any()).Returns(0); + mock.GetToken(Any()).Returns(0); mock.Object.GetToken("a", out _); mock.Object.GetToken("b", out _); @@ -279,7 +279,7 @@ public async Task Out_ReadOnlySpan_Char_Verification_Multiple() mock.GetToken("a").WasCalled(Times.Exactly(2)); mock.GetToken("b").WasCalled(Times.Once); - mock.GetToken(Arg.Any()).WasCalled(Times.Exactly(3)); + mock.GetToken(Any()).WasCalled(Times.Exactly(3)); mock.GetToken("z").WasNeverCalled(); await Assert.That(true).IsTrue(); } @@ -350,7 +350,7 @@ public async Task Multiple_Out_Only_Span_Set() public async Task Multiple_Out_Chain_Order_SetsOut_Before_Returns() { var mock = Mock.Of(); - mock.Extract(Arg.Any()) + mock.Extract(Any()) .SetsOutCount(99) .SetsOutData(new ReadOnlySpan([0xFF])) .Returns(true); @@ -400,7 +400,7 @@ public async Task Multiple_Out_Callback_With_Args() { string? capturedInput = null; var mock = Mock.Of(); - mock.Extract(Arg.Any()) + mock.Extract(Any()) .Callback((Action)(args => capturedInput = (string?)args[0])) .Returns(true) .SetsOutCount(1) @@ -427,7 +427,7 @@ public async Task Multiple_Out_Throws_Exception() public async Task Multiple_Out_Verification() { var mock = Mock.Of(); - mock.Extract(Arg.Any()).Returns(false); + mock.Extract(Any()).Returns(false); mock.Object.Extract("a", out _, out _); mock.Object.Extract("b", out _, out _); @@ -435,7 +435,7 @@ public async Task Multiple_Out_Verification() mock.Extract("a").WasCalled(Times.Exactly(2)); mock.Extract("b").WasCalled(Times.Once); - mock.Extract(Arg.Any()).WasCalled(Times.Exactly(3)); + mock.Extract(Any()).WasCalled(Times.Exactly(3)); mock.Extract("c").WasNeverCalled(); await Assert.That(true).IsTrue(); } @@ -513,7 +513,7 @@ public async Task Ref_Int_And_Out_Span_Different_Offsets() public async Task Ref_And_Out_Span_With_Any_Matcher() { var mock = Mock.Of(); - mock.Decode(Arg.Any()) + mock.Decode(Any()) .Returns(true) .SetsRefOffset(100) .SetsOutDecoded(new ReadOnlySpan([0xFF])); @@ -531,7 +531,7 @@ public async Task Ref_And_Out_Span_With_Any_Matcher() public async Task Ref_And_Out_Span_Verification() { var mock = Mock.Of(); - mock.Decode(Arg.Any()).Returns(false); + mock.Decode(Any()).Returns(false); int p1 = 0, p2 = 5; mock.Object.Decode(ref p1, out _); @@ -539,7 +539,7 @@ public async Task Ref_And_Out_Span_Verification() mock.Decode(0).WasCalled(Times.Once); mock.Decode(5).WasCalled(Times.Once); - mock.Decode(Arg.Any()).WasCalled(Times.Exactly(2)); + mock.Decode(Any()).WasCalled(Times.Exactly(2)); mock.Decode(99).WasNeverCalled(); await Assert.That(true).IsTrue(); } @@ -562,7 +562,7 @@ public async Task Ref_And_Out_Span_Callback() { var wasCalled = false; var mock = Mock.Of(); - mock.Decode(Arg.Any()) + mock.Decode(Any()) .Callback(() => wasCalled = true) .Returns(true) .SetsOutDecoded(new ReadOnlySpan([1])); @@ -675,7 +675,7 @@ public class ComprehensiveRefTests public async Task Ref_With_Return_Value() { var mock = Mock.Of(); - mock.Increment(Arg.Any(), 1) + mock.Increment(Any(), 1) .Returns(1) .SetsRefValue(11); @@ -709,11 +709,11 @@ public async Task Ref_Exact_Value_Matching() public async Task Ref_Predicate_Matching() { var mock = Mock.Of(); - mock.TryAdvance(Arg.Is(v => v >= 0), 100) + mock.TryAdvance(Is(v => v >= 0), 100) .Returns(true) .SetsRefPosition(50); - mock.TryAdvance(Arg.Is(v => v < 0), Arg.Any()) + mock.TryAdvance(Is(v => v < 0), Any()) .Returns(false); // Positive position @@ -732,7 +732,7 @@ public async Task Ref_Predicate_Matching() public async Task Ref_Void_Method() { var mock = Mock.Of(); - mock.Clear(Arg.Any()).SetsRefValue(0); + mock.Clear(Any()).SetsRefValue(0); int val = 42; mock.Object.Clear(ref val); @@ -758,7 +758,7 @@ public async Task Ref_Callback_Fires() { var wasCalled = false; var mock = Mock.Of(); - mock.Clear(Arg.Any()) + mock.Clear(Any()) .Callback(() => wasCalled = true) .SetsRefValue(0); @@ -773,7 +773,7 @@ public async Task Ref_Callback_Fires() public async Task Ref_Throws() { var mock = Mock.Of(); - mock.Increment(Arg.Any(), 0).Throws(); + mock.Increment(Any(), 0).Throws(); int val = 1; var ex = Assert.Throws(() => mock.Object.Increment(ref val, 0)); @@ -785,7 +785,7 @@ public async Task Ref_Throws() public async Task Ref_Verification_With_Exact_Value() { var mock = Mock.Of(); - mock.Increment(Arg.Any(), Arg.Any()).Returns(0); + mock.Increment(Any(), Any()).Returns(0); int v1 = 5, v2 = 10; mock.Object.Increment(ref v1, 1); @@ -794,7 +794,7 @@ public async Task Ref_Verification_With_Exact_Value() mock.Increment(5, 1).WasCalled(Times.Exactly(2)); mock.Increment(10, 2).WasCalled(Times.Once); - mock.Increment(Arg.Any(), Arg.Any()).WasCalled(Times.Exactly(3)); + mock.Increment(Any(), Any()).WasCalled(Times.Exactly(3)); await Assert.That(true).IsTrue(); } @@ -802,16 +802,16 @@ public async Task Ref_Verification_With_Exact_Value() public async Task Ref_Verification_AtLeast_AtMost() { var mock = Mock.Of(); - mock.Clear(Arg.Any()); + mock.Clear(Any()); int v = 1; mock.Object.Clear(ref v); mock.Object.Clear(ref v); mock.Object.Clear(ref v); - mock.Clear(Arg.Any()).WasCalled(Times.AtLeast(2)); - mock.Clear(Arg.Any()).WasCalled(Times.AtMost(5)); - mock.Clear(Arg.Any()).WasCalled(Times.Between(2, 4)); + mock.Clear(Any()).WasCalled(Times.AtLeast(2)); + mock.Clear(Any()).WasCalled(Times.AtMost(5)); + mock.Clear(Any()).WasCalled(Times.Between(2, 4)); await Assert.That(true).IsTrue(); } } @@ -841,7 +841,7 @@ public async Task Ref_And_Out_Both_Set() public async Task Ref_And_Out_Only_Ref_Set() { var mock = Mock.Of(); - mock.SwapAndReport(Arg.Any()).SetsRefValue(99); + mock.SwapAndReport(Any()).SetsRefValue(99); int val = 1; mock.Object.SwapAndReport(ref val, out var report); @@ -854,7 +854,7 @@ public async Task Ref_And_Out_Only_Ref_Set() public async Task Ref_And_Out_Only_Out_Set() { var mock = Mock.Of(); - mock.SwapAndReport(Arg.Any()).SetsOutReport("report"); + mock.SwapAndReport(Any()).SetsOutReport("report"); int val = 50; mock.Object.SwapAndReport(ref val, out var report); @@ -869,7 +869,7 @@ public async Task Ref_And_Out_Callback() { var wasCalled = false; var mock = Mock.Of(); - mock.SwapAndReport(Arg.Any()) + mock.SwapAndReport(Any()) .Callback(() => wasCalled = true) .SetsRefValue(0) .SetsOutReport("done"); @@ -905,7 +905,7 @@ public async Task Ref_And_Out_Verification() mock.SwapAndReport(1).WasCalled(Times.Exactly(2)); mock.SwapAndReport(2).WasCalled(Times.Once); - mock.SwapAndReport(Arg.Any()).WasCalled(Times.Exactly(3)); + mock.SwapAndReport(Any()).WasCalled(Times.Exactly(3)); await Assert.That(true).IsTrue(); } } @@ -978,7 +978,7 @@ public async Task Two_Span_Out_Verification() mock.Split("a").WasCalled(Times.Once); mock.Split("b").WasCalled(Times.Once); - mock.Split(Arg.Any()).WasCalled(Times.Exactly(2)); + mock.Split(Any()).WasCalled(Times.Exactly(2)); mock.Split("c").WasNeverCalled(); await Assert.That(true).IsTrue(); } @@ -1007,7 +1007,7 @@ public async Task Then_Returns_Sequence_With_Out_Span() { // SetsOut applies at setup level; Then() sequences Returns/Callback/Throws var mock = Mock.Of(); - mock.TryParse(Arg.Any()) + mock.TryParse(Any()) .SetsOutData(new ReadOnlySpan([0xAA])) .Returns(true) .Then() @@ -1079,7 +1079,7 @@ public async Task Then_Callback_Sequence_With_Out_Span() public async Task ReturnsSequentially_With_Out_Span() { var mock = Mock.Of(); - mock.TryParse(Arg.Any()) + mock.TryParse(Any()) .ReturnsSequentially(true, true, false) .SetsOutData(new ReadOnlySpan([0x01, 0x02])); @@ -1324,7 +1324,7 @@ public async Task Out_Span_Different_Values_Per_Input() public async Task Out_Span_Verification_With_Mixed_Params() { var mock = Mock.Of(); - mock.TryParse(Arg.Any()).Returns(false); + mock.TryParse(Any()).Returns(false); mock.Object.TryParse("x", out _); mock.Object.TryParse("y", out _); @@ -1332,7 +1332,7 @@ public async Task Out_Span_Verification_With_Mixed_Params() mock.TryParse("x").WasCalled(Times.Exactly(2)); mock.TryParse("y").WasCalled(Times.Once); - mock.TryParse(Arg.Any()).WasCalled(Times.Exactly(3)); + mock.TryParse(Any()).WasCalled(Times.Exactly(3)); mock.TryParse("z").WasNeverCalled(); await Assert.That(true).IsTrue(); } @@ -1342,7 +1342,7 @@ public async Task Out_Span_Callback_With_Args() { string? capturedInput = null; var mock = Mock.Of(); - mock.TryParse(Arg.Any()) + mock.TryParse(Any()) .Callback((Action)(args => capturedInput = (string?)args[0])) .Returns(true) .SetsOutData(new ReadOnlySpan([1])); @@ -1356,7 +1356,7 @@ public async Task Out_Span_Callback_With_Args() public async Task Out_Span_Throws_Exception_Factory() { var mock = Mock.Of(); - mock.TryParse(Arg.Any()) + mock.TryParse(Any()) .Throws((Func)(args => new ArgumentException($"Bad input: {args[0]}"))); diff --git a/TUnit.Mocks.Tests/CustomMatcherTests.cs b/TUnit.Mocks.Tests/CustomMatcherTests.cs index ce32217ce7..7793453b72 100644 --- a/TUnit.Mocks.Tests/CustomMatcherTests.cs +++ b/TUnit.Mocks.Tests/CustomMatcherTests.cs @@ -55,7 +55,7 @@ public async Task Custom_String_Length_Matcher() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Matches(new StringLengthMatcher(3, 10))).Returns("valid"); + mock.Greet(Matches(new StringLengthMatcher(3, 10))).Returns("valid"); // Act var greeter = mock.Object; @@ -72,7 +72,7 @@ public async Task Custom_Range_Matcher_With_Calculator() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Matches(new RangeMatcher(1, 10)), Arg.Any()).Returns(100); + mock.Add(Matches(new RangeMatcher(1, 10)), Any()).Returns(100); // Act var calc = mock.Object; @@ -90,7 +90,7 @@ public async Task Custom_Matcher_With_Predicate_Combined() { // Arrange — custom matcher for first arg, predicate for second var mock = Mock.Of(); - mock.Add(Arg.Matches(new RangeMatcher(0, 100)), Arg.Is(b => b > 0)).Returns(42); + mock.Add(Matches(new RangeMatcher(0, 100)), Is(b => b > 0)).Returns(42); // Act var calc = mock.Object; @@ -117,7 +117,7 @@ public async Task Custom_Matcher_With_Verification() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(0); + mock.Add(Any(), Any()).Returns(0); // Act var calc = mock.Object; @@ -125,7 +125,7 @@ public async Task Custom_Matcher_With_Verification() calc.Add(50, 100); // Verify — using custom matcher in verification - mock.Add(Arg.Matches(new RangeMatcher(1, 10)), Arg.Any()).WasCalled(Times.Once); - mock.Add(Arg.Matches(new RangeMatcher(40, 60)), Arg.Any()).WasCalled(Times.Once); + mock.Add(Matches(new RangeMatcher(1, 10)), Any()).WasCalled(Times.Once); + mock.Add(Matches(new RangeMatcher(40, 60)), Any()).WasCalled(Times.Once); } } diff --git a/TUnit.Mocks.Tests/DelegateMockTests.cs b/TUnit.Mocks.Tests/DelegateMockTests.cs index bbc9b977c0..71d1d44941 100644 --- a/TUnit.Mocks.Tests/DelegateMockTests.cs +++ b/TUnit.Mocks.Tests/DelegateMockTests.cs @@ -8,7 +8,7 @@ public class DelegateMockTests public async Task Func_Returns_Configured_Value() { var mock = Mock.OfDelegate>(); - mock.Invoke(Arg.Any()).Returns(42); + mock.Invoke(Any()).Returns(42); var result = mock.Object("hello"); @@ -39,7 +39,7 @@ public async Task Action_Can_Be_Invoked_And_Verified() public async Task Custom_Delegate_Returns_Configured_Value() { var mock = Mock.OfDelegate(); - mock.Invoke(Arg.Any(), Arg.Any()).Returns(100); + mock.Invoke(Any(), Any()).Returns(100); var result = mock.Object(3, 5); @@ -50,7 +50,7 @@ public async Task Custom_Delegate_Returns_Configured_Value() public async Task Func_Throws_When_Configured() { var mock = Mock.OfDelegate>(); - mock.Invoke(Arg.Any()).Throws(); + mock.Invoke(Any()).Throws(); var act = () => mock.Object("test"); @@ -63,7 +63,7 @@ public async Task Func_Callback_Fires() var mock = Mock.OfDelegate>(); var callbackFired = false; - mock.Invoke(Arg.Any()) + mock.Invoke(Any()) .Callback(() => callbackFired = true) .Then() .Returns(1); @@ -77,7 +77,7 @@ public async Task Func_Callback_Fires() public async Task Func_Arg_Capture_Works() { var mock = Mock.OfDelegate>(); - var nameArg = Arg.Any(); + var nameArg = Any(); mock.Invoke(nameArg).Returns(1); mock.Object("first"); @@ -92,12 +92,12 @@ public async Task Func_Arg_Capture_Works() public async Task Func_Verify_WasCalled() { var mock = Mock.OfDelegate>(); - mock.Invoke(Arg.Any()).Returns(1); + mock.Invoke(Any()).Returns(1); mock.Object("a"); mock.Object("b"); - mock.Invoke(Arg.Any()).WasCalled(Times.Exactly(2)); + mock.Invoke(Any()).WasCalled(Times.Exactly(2)); } [Test] @@ -114,7 +114,7 @@ public async Task Action_Strict_Mode_Throws_On_Unconfigured_Call() public async Task Func_Implicit_Conversion_Works() { var mock = Mock.OfDelegate>(); - mock.Invoke(Arg.Any()).Returns(99); + mock.Invoke(Any()).Returns(99); Func func = mock; var result = func(5); diff --git a/TUnit.Mocks.Tests/DiagnosticsTests.cs b/TUnit.Mocks.Tests/DiagnosticsTests.cs index b9d98a6f16..3f1a071a4f 100644 --- a/TUnit.Mocks.Tests/DiagnosticsTests.cs +++ b/TUnit.Mocks.Tests/DiagnosticsTests.cs @@ -47,7 +47,7 @@ public async Task All_Setups_Exercised() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; _ = calc.Add(1, 2); @@ -78,7 +78,7 @@ public async Task No_Calls_Means_All_Setups_Unused() public async Task Matcher_Descriptions_Populated() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Is(x => x > 0)).Returns(1); + mock.Add(Any(), Is(x => x > 0)).Returns(1); var diag = Mock.GetDiagnostics(mock); diff --git a/TUnit.Mocks.Tests/EdgeCaseTests.cs b/TUnit.Mocks.Tests/EdgeCaseTests.cs index 651a718ae0..6b60e898b3 100644 --- a/TUnit.Mocks.Tests/EdgeCaseTests.cs +++ b/TUnit.Mocks.Tests/EdgeCaseTests.cs @@ -169,9 +169,9 @@ public async Task Overload_Void_With_Optional_Bool() var singleArgCalled = false; var twoArgCalled = false; var mock = Mock.Of(); - mock.Process(Arg.Any()) + mock.Process(Any()) .Callback(() => singleArgCalled = true); - mock.Process(Arg.Any(), Arg.Any()) + mock.Process(Any(), Any()) .Callback(() => twoArgCalled = true); // Act @@ -189,8 +189,8 @@ public async Task Overload_Verify_Specific_Overload() { // Arrange var mock = Mock.Of(); - mock.Format(Arg.Any()).Returns("int"); - mock.Format(Arg.Any()).Returns("string"); + mock.Format(Any()).Returns("int"); + mock.Format(Any()).Returns("string"); // Act IOverloadedService svc = mock.Object; @@ -199,8 +199,8 @@ public async Task Overload_Verify_Specific_Overload() svc.Format("abc"); // Assert — verify only the int overload was called twice - mock.Format(Arg.Any()).WasCalled(Times.Exactly(2)); - mock.Format(Arg.Any()).WasCalled(Times.Once); + mock.Format(Any()).WasCalled(Times.Exactly(2)); + mock.Format(Any()).WasCalled(Times.Once); await Assert.That(true).IsTrue(); } } @@ -217,10 +217,10 @@ public async Task Seven_Parameter_Method_With_Mixed_Matchers() var mock = Mock.Of(); mock.BuildQuery( "users", - Arg.Any(), - Arg.Is(w => w != null && w.Contains("active")), - Arg.Any(), - Arg.Any(), + Any(), + Is(w => w != null && w.Contains("active")), + Any(), + Any(), "name", true ).Returns("SELECT * FROM users WHERE active ORDER BY name ASC"); @@ -260,11 +260,11 @@ public async Task Payment_Method_With_CancellationToken_And_Nullable() var mock = Mock.Of(); mock.ProcessPaymentAsync( "merchant-123", - Arg.Is(a => a > 0), + Is(a => a > 0), "USD", - Arg.Any(), - Arg.Any(), - Arg.Any() + Any(), + Any(), + Any() ).Returns(true); // Act @@ -285,11 +285,11 @@ public async Task Payment_Method_With_CancellationToken_And_Nullable() // Verify the call was made mock.ProcessPaymentAsync( "merchant-123", - Arg.Any(), + Any(), "USD", - Arg.Any(), - Arg.Any(), - Arg.Any() + Any(), + Any(), + Any() ).WasCalled(Times.Once); await Assert.That(true).IsTrue(); } @@ -425,7 +425,7 @@ public async Task Async_Throws_Returns_Faulted_Task_Not_Sync_Throw() { // Arrange var mock = Mock.Of(); - mock.CallRemoteServiceAsync(Arg.Any()).Throws(); + mock.CallRemoteServiceAsync(Any()).Throws(); // Act — the method should return a faulted task, NOT throw synchronously IExternalApi api = mock.Object; @@ -479,7 +479,7 @@ public async Task Predicate_Matcher_String_Length_Range() { // Arrange var mock = Mock.Of(); - mock.Validate(Arg.Is(s => s != null && s.Length >= 3 && s.Length <= 50)).Returns(true); + mock.Validate(Is(s => s != null && s.Length >= 3 && s.Length <= 50)).Returns(true); // Act IValidator validator = mock.Object; @@ -501,8 +501,8 @@ public async Task Multiple_Predicates_On_Multiple_Args() // Arrange var mock = Mock.Of(); mock.Score( - Arg.Is(t => t != null && t.Length > 0), - Arg.Is(w => w >= 1 && w <= 10) + Is(t => t != null && t.Length > 0), + Is(w => w >= 1 && w <= 10) ).Returns(100); // Act @@ -524,7 +524,7 @@ public async Task Exact_Match_Overrides_Any_Match_Later_Wins() { // Arrange — setup Any first, then exact. Last setup wins for matching args. var mock = Mock.Of(); - mock.Validate(Arg.Any()).Returns(false); + mock.Validate(Any()).Returns(false); mock.Validate("special").Returns(true); // Act @@ -540,7 +540,7 @@ public async Task Exact_Match_Overrides_Any_Match_Later_Wins() public async Task Arg_Capture_Across_Multiple_Calls_Verifies_All() { // Arrange - var input = Arg.Any(); + var input = Any(); var mock = Mock.Of(); mock.Validate(input).Returns(true); @@ -635,7 +635,7 @@ public async Task Concurrent_Setup_And_Invocation_From_Multiple_Threads() { // Arrange var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns(42); + mock.GetValue(Any()).Returns(42); IConcurrentService svc = mock.Object; // Act — 10 threads setting up and calling simultaneously @@ -663,7 +663,7 @@ public async Task Concurrent_Verification_After_Parallel_Calls() { // Arrange var mock = Mock.Of(); - mock.IncrementAsync(Arg.Any()).Returns(1); + mock.IncrementAsync(Any()).Returns(1); IConcurrentService svc = mock.Object; // Act — 100 parallel calls @@ -719,8 +719,8 @@ public async Task Enum_Parameter_With_Predicate_Matcher() { // Arrange var mock = Mock.Of(); - mock.CountByStatusAsync(Arg.Is(s => s == Status.Active || s == Status.Pending)).Returns(10); - mock.CountByStatusAsync(Arg.Is(s => s == Status.Completed || s == Status.Failed)).Returns(5); + mock.CountByStatusAsync(Is(s => s == Status.Active || s == Status.Pending)).Returns(10); + mock.CountByStatusAsync(Is(s => s == Status.Completed || s == Status.Failed)).Returns(5); // Act ITaskManager mgr = mock.Object; @@ -747,10 +747,10 @@ public async Task Enum_Void_Method_Verify_Specific_Value() mgr.UpdateStatus(3, Status.Active); // Assert — verify UpdateStatus called with specific Status values - mock.UpdateStatus(Arg.Any(), Status.Active).WasCalled(Times.Exactly(2)); - mock.UpdateStatus(Arg.Any(), Status.Completed).WasCalled(Times.Once); - mock.UpdateStatus(Arg.Any(), Status.Failed).WasNeverCalled(); - mock.UpdateStatus(Arg.Any(), Status.Pending).WasNeverCalled(); + mock.UpdateStatus(Any(), Status.Active).WasCalled(Times.Exactly(2)); + mock.UpdateStatus(Any(), Status.Completed).WasCalled(Times.Once); + mock.UpdateStatus(Any(), Status.Failed).WasNeverCalled(); + mock.UpdateStatus(Any(), Status.Pending).WasNeverCalled(); // Verify specific task ID + status combination mock.UpdateStatus(1, Status.Active).WasCalled(Times.Once); diff --git a/TUnit.Mocks.Tests/ErrorMessageTests.cs b/TUnit.Mocks.Tests/ErrorMessageTests.cs index dbe833fea0..36b4ff79c8 100644 --- a/TUnit.Mocks.Tests/ErrorMessageTests.cs +++ b/TUnit.Mocks.Tests/ErrorMessageTests.cs @@ -150,7 +150,7 @@ public async Task Verification_Actual_Calls_List_Is_Populated() // Act & Assert — verify with Arg.Any but wrong count var exception = Assert.Throws(() => { - mock.Add(Arg.Any(), Arg.Any()).WasCalled(Times.Exactly(5)); + mock.Add(Any(), Any()).WasCalled(Times.Exactly(5)); }); await Assert.That(exception.ActualCount).IsEqualTo(2); diff --git a/TUnit.Mocks.Tests/GenericTests.cs b/TUnit.Mocks.Tests/GenericTests.cs index c05e4717c3..a7484ba373 100644 --- a/TUnit.Mocks.Tests/GenericTests.cs +++ b/TUnit.Mocks.Tests/GenericTests.cs @@ -107,7 +107,7 @@ public async Task Generic_Void_Method_Verify() repo.Save(customer); // Assert - mock.Save(Arg.Any()).WasCalled(Times.Once); + mock.Save(Any()).WasCalled(Times.Once); await Assert.That(true).IsTrue(); } @@ -117,7 +117,7 @@ public async Task Generic_Method_With_Any_Matcher() // Arrange var mock = Mock.Of(); var customer = new Customer { Id = 1, Name = "Any" }; - mock.Get(Arg.Any()).Returns(customer); + mock.Get(Any()).Returns(customer); // Act IRepository repo = mock.Object; @@ -135,7 +135,7 @@ public async Task Generic_Method_With_Two_Type_Parameters() // Arrange var mock = Mock.Of(); var order = new Order { OrderId = 10 }; - mock.Transform(Arg.Any()).Returns(order); + mock.Transform(Any()).Returns(order); // Act IRepository repo = mock.Object; diff --git a/TUnit.Mocks.Tests/ImplicitArgConversionTests.cs b/TUnit.Mocks.Tests/ImplicitArgConversionTests.cs new file mode 100644 index 0000000000..b4d6b9d5d1 --- /dev/null +++ b/TUnit.Mocks.Tests/ImplicitArgConversionTests.cs @@ -0,0 +1,347 @@ +using TUnit.Mocks.Arguments; + +namespace TUnit.Mocks.Tests; + +/// +/// Tests for implicit conversions on : +/// - T value → Arg{T} (exact matching via ExactMatcher) +/// - Func{T?, bool} → Arg{T} (predicate matching via PredicateMatcher) +/// +public class ImplicitArgConversionTests +{ + // ────────────────────────────────────────────── + // Implicit T → Arg (exact value matching) + // ────────────────────────────────────────────── + + [Test] + public async Task Implicit_Value_Int_Matches_Exact() + { + var mock = Mock.Of(); + mock.Add(2, 3).Returns(5); + + await Assert.That(mock.Object.Add(2, 3)).IsEqualTo(5); + } + + [Test] + public async Task Implicit_Value_Int_Does_Not_Match_Different_Value() + { + var mock = Mock.Of(); + mock.Add(2, 3).Returns(5); + + await Assert.That(mock.Object.Add(2, 4)).IsEqualTo(0); + await Assert.That(mock.Object.Add(3, 3)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Value_String_Matches_Exact() + { + var mock = Mock.Of(); + mock.Greet("Alice").Returns("Hello, Alice!"); + + await Assert.That(mock.Object.Greet("Alice")).IsEqualTo("Hello, Alice!"); + } + + [Test] + public async Task Implicit_Value_String_Does_Not_Match_Different_Value() + { + var mock = Mock.Of(); + mock.Greet("Alice").Returns("Hello, Alice!"); + + await Assert.That(mock.Object.Greet("Bob")).IsNotEqualTo("Hello, Alice!"); + await Assert.That(mock.Object.Greet("alice")).IsNotEqualTo("Hello, Alice!"); + await Assert.That(mock.Object.Greet("")).IsNotEqualTo("Hello, Alice!"); + } + + [Test] + public async Task Implicit_Value_Zero_Matches() + { + var mock = Mock.Of(); + mock.Add(0, 0).Returns(99); + + await Assert.That(mock.Object.Add(0, 0)).IsEqualTo(99); + } + + [Test] + public async Task Implicit_Value_Negative_Numbers_Match() + { + var mock = Mock.Of(); + mock.Add(-1, -2).Returns(-3); + + await Assert.That(mock.Object.Add(-1, -2)).IsEqualTo(-3); + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Value_Empty_String_Matches() + { + var mock = Mock.Of(); + mock.Greet("").Returns("empty"); + + await Assert.That(mock.Object.Greet("")).IsEqualTo("empty"); + await Assert.That(mock.Object.Greet(" ")).IsNotEqualTo("empty"); + } + + [Test] + public async Task Implicit_Value_Mixed_With_Explicit_Matcher() + { + var mock = Mock.Of(); + mock.Add(Any(), 5).Returns(50); + + await Assert.That(mock.Object.Add(0, 5)).IsEqualTo(50); + await Assert.That(mock.Object.Add(999, 5)).IsEqualTo(50); + await Assert.That(mock.Object.Add(0, 6)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Value_Multiple_Setups_Last_Wins() + { + var mock = Mock.Of(); + mock.Add(1, 1).Returns(10); + mock.Add(1, 1).Returns(20); + + await Assert.That(mock.Object.Add(1, 1)).IsEqualTo(20); + } + + [Test] + public async Task Implicit_Value_IntMaxValue_And_IntMinValue() + { + var mock = Mock.Of(); + mock.Add(int.MaxValue, int.MinValue).Returns(1); + + await Assert.That(mock.Object.Add(int.MaxValue, int.MinValue)).IsEqualTo(1); + await Assert.That(mock.Object.Add(int.MinValue, int.MaxValue)).IsEqualTo(0); + } + + // ────────────────────────────────────────────── + // Implicit Func → Arg (predicate) + // Using inline lambdas — the primary usage pattern + // ────────────────────────────────────────────── + + [Test] + public async Task Implicit_Predicate_Int_Greater_Than_Matches() + { + var mock = Mock.Of(); + mock.Add(Is(x => x > 5), Any()).Returns(100); + + await Assert.That(mock.Object.Add(6, 0)).IsEqualTo(100); + await Assert.That(mock.Object.Add(10, 0)).IsEqualTo(100); + await Assert.That(mock.Object.Add(int.MaxValue, 0)).IsEqualTo(100); + } + + [Test] + public async Task Implicit_Predicate_Int_Greater_Than_Does_Not_Match() + { + var mock = Mock.Of(); + mock.Add(Is(x => x > 5), Any()).Returns(100); + + await Assert.That(mock.Object.Add(5, 0)).IsEqualTo(0); + await Assert.That(mock.Object.Add(0, 0)).IsEqualTo(0); + await Assert.That(mock.Object.Add(-1, 0)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Predicate_String_StartsWith_Matches() + { + var mock = Mock.Of(); + Func startsWithHi = s => s != null && s.StartsWith("Hi"); + mock.Greet(startsWithHi).Returns("matched"); + + await Assert.That(mock.Object.Greet("Hi there")).IsEqualTo("matched"); + await Assert.That(mock.Object.Greet("Hi")).IsEqualTo("matched"); + } + + [Test] + public async Task Implicit_Predicate_String_StartsWith_Does_Not_Match() + { + var mock = Mock.Of(); + Func startsWithHi = s => s != null && s.StartsWith("Hi"); + mock.Greet(startsWithHi).Returns("matched"); + + await Assert.That(mock.Object.Greet("Hello")).IsNotEqualTo("matched"); + await Assert.That(mock.Object.Greet("hi there")).IsNotEqualTo("matched"); + await Assert.That(mock.Object.Greet("")).IsNotEqualTo("matched"); + } + + [Test] + public async Task Implicit_Predicate_Both_Args() + { + var mock = Mock.Of(); + mock.Add(Is(x => x > 0), Is(x => x % 2 == 0)).Returns(42); + + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(42); + await Assert.That(mock.Object.Add(3, 4)).IsEqualTo(42); + + // First arg not positive + await Assert.That(mock.Object.Add(0, 2)).IsEqualTo(0); + await Assert.That(mock.Object.Add(-1, 2)).IsEqualTo(0); + + // Second arg not even + await Assert.That(mock.Object.Add(1, 1)).IsEqualTo(0); + await Assert.That(mock.Object.Add(1, 3)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Predicate_String_Contains() + { + var mock = Mock.Of(); + Func containsWorld = s => s != null && s.Contains("world"); + mock.Greet(containsWorld).Returns("has world"); + + await Assert.That(mock.Object.Greet("hello world")).IsEqualTo("has world"); + await Assert.That(mock.Object.Greet("world")).IsEqualTo("has world"); + await Assert.That(mock.Object.Greet("hello")).IsNotEqualTo("has world"); + } + + [Test] + public async Task Implicit_Predicate_String_Length_Check() + { + var mock = Mock.Of(); + Func shortString = s => s != null && s.Length <= 3; + mock.Greet(shortString).Returns("short"); + + await Assert.That(mock.Object.Greet("Hi")).IsEqualTo("short"); + await Assert.That(mock.Object.Greet("abc")).IsEqualTo("short"); + await Assert.That(mock.Object.Greet("abcd")).IsNotEqualTo("short"); + } + + [Test] + public async Task Implicit_Predicate_Always_True_Matches_Everything() + { + var mock = Mock.Of(); + Func always = _ => true; + mock.Greet(always).Returns("always"); + + await Assert.That(mock.Object.Greet("anything")).IsEqualTo("always"); + await Assert.That(mock.Object.Greet("")).IsEqualTo("always"); + } + + [Test] + public async Task Implicit_Predicate_Always_False_Matches_Nothing() + { + var mock = Mock.Of(); + Func never = _ => false; + mock.Greet(never).Returns("never"); + + await Assert.That(mock.Object.Greet("anything")).IsNotEqualTo("never"); + await Assert.That(mock.Object.Greet("")).IsNotEqualTo("never"); + } + + [Test] + public async Task Implicit_Predicate_With_Closure() + { + var threshold = 10; + var mock = Mock.Of(); + mock.Add(Is(x => x > threshold), Any()).Returns(1); + + await Assert.That(mock.Object.Add(11, 0)).IsEqualTo(1); + await Assert.That(mock.Object.Add(10, 0)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Predicate_Int_Range_Check() + { + var mock = Mock.Of(); + mock.Add(Is(x => x >= 1 && x <= 10), Is(x => x >= 1 && x <= 10)).Returns(50); + + await Assert.That(mock.Object.Add(1, 10)).IsEqualTo(50); + await Assert.That(mock.Object.Add(5, 5)).IsEqualTo(50); + await Assert.That(mock.Object.Add(0, 5)).IsEqualTo(0); + await Assert.That(mock.Object.Add(5, 11)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Predicate_Handles_Null_String() + { + var mock = Mock.Of(); + Func isNull = s => s is null; + mock.Greet(isNull).Returns("was null"); + + await Assert.That(mock.Object.Greet(null!)).IsEqualTo("was null"); + await Assert.That(mock.Object.Greet("not null")).IsNotEqualTo("was null"); + } + + [Test] + public async Task Implicit_Predicate_Func_Variable_For_String() + { + // Verifies the implicit Func → Arg operator works via variable + var mock = Mock.Of(); + Func predicate = s => s != null && s.Length > 3; + mock.Greet(predicate).Returns("long name"); + + await Assert.That(mock.Object.Greet("Alice")).IsEqualTo("long name"); + await Assert.That(mock.Object.Greet("Bob")).IsNotEqualTo("long name"); + } + + [Test] + public async Task Implicit_Predicate_Func_Variable_Multiple_Setups() + { + var mock = Mock.Of(); + Func startsA = s => s != null && s.StartsWith("A"); + Func startsB = s => s != null && s.StartsWith("B"); + mock.Greet(startsA).Returns("A-name"); + mock.Greet(startsB).Returns("B-name"); + + // Last matching setup wins + await Assert.That(mock.Object.Greet("Alice")).IsEqualTo("A-name"); + await Assert.That(mock.Object.Greet("Bob")).IsEqualTo("B-name"); + } + + // ────────────────────────────────────────────── + // Mixing implicit value and implicit predicate + // ────────────────────────────────────────────── + + [Test] + public async Task Implicit_Value_And_Predicate_Mixed() + { + var mock = Mock.Of(); + mock.Add(10, Is(x => x % 2 == 0)).Returns(77); + + // First arg must be exactly 10, second must be even + await Assert.That(mock.Object.Add(10, 2)).IsEqualTo(77); + await Assert.That(mock.Object.Add(10, 100)).IsEqualTo(77); + + // First arg not 10 + await Assert.That(mock.Object.Add(11, 2)).IsEqualTo(0); + + // Second arg not even + await Assert.That(mock.Object.Add(10, 3)).IsEqualTo(0); + } + + [Test] + public async Task Implicit_Predicate_Overrides_Earlier_Value_Setup() + { + var mock = Mock.Of(); + mock.Add(5, 5).Returns(10); + mock.Add(Is(_ => true), Is(_ => true)).Returns(99); + + // Last setup wins + await Assert.That(mock.Object.Add(5, 5)).IsEqualTo(99); + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(99); + } + + [Test] + public async Task Implicit_Value_Overrides_Earlier_Predicate_Setup() + { + var mock = Mock.Of(); + mock.Add(Is(_ => true), Is(_ => true)).Returns(99); + mock.Add(5, 5).Returns(10); + + // Last setup wins for (5,5), predicate still catches others + await Assert.That(mock.Object.Add(5, 5)).IsEqualTo(10); + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(99); + } + + [Test] + public async Task Implicit_Predicate_With_Capture_On_Other_Arg() + { + var captured = Any(); + var mock = Mock.Of(); + mock.Add(captured, Is(x => x > 0)).Returns(1); + + mock.Object.Add(42, 1); + mock.Object.Add(99, -1); // won't match — second arg not positive + + await Assert.That(captured.Values).Count().IsEqualTo(1); + await Assert.That(captured.Values[0]).IsEqualTo(42); + } +} diff --git a/TUnit.Mocks.Tests/InParameterTests.cs b/TUnit.Mocks.Tests/InParameterTests.cs index e13c04c6c7..ee5dfd3090 100644 --- a/TUnit.Mocks.Tests/InParameterTests.cs +++ b/TUnit.Mocks.Tests/InParameterTests.cs @@ -46,7 +46,7 @@ public async Task In_Params_Returns_Value() public async Task In_Params_Arg_Any_Matching() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); var result = mock.Object.Add(10, 20); @@ -72,7 +72,7 @@ public async Task In_Params_Void_Method() { var wasCalled = false; var mock = Mock.Of(); - mock.Log(Arg.Any()).Callback(() => wasCalled = true); + mock.Log(Any()).Callback(() => wasCalled = true); mock.Object.Log("hello"); @@ -83,7 +83,7 @@ public async Task In_Params_Void_Method() public void In_Params_Throws() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Throws(); + mock.Add(Any(), Any()).Throws(); Assert.Throws(() => mock.Object.Add(1, 2)); } @@ -92,12 +92,12 @@ public void In_Params_Throws() public async Task In_Params_Verify_WasCalled() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(0); + mock.Add(Any(), Any()).Returns(0); mock.Object.Add(1, 2); mock.Object.Add(3, 4); - mock.Add(Arg.Any(), Arg.Any()).WasCalled(Times.Exactly(2)); + mock.Add(Any(), Any()).WasCalled(Times.Exactly(2)); await Assert.That(true).IsTrue(); } @@ -114,7 +114,7 @@ public async Task In_Params_Verify_WasNeverCalled() public async Task In_Params_Mixed_With_Regular_Params() { var mock = Mock.Of(); - mock.Compute(Arg.Any(), Arg.Any()).Returns(99.5); + mock.Compute(Any(), Any()).Returns(99.5); var result = mock.Object.Compute(42, 2.0); @@ -139,7 +139,7 @@ public async Task In_Params_Specific_Mixed_Matching() public async Task In_Struct_Params() { var mock = Mock.Of(); - mock.Distance(Arg.Any(), Arg.Any()).Returns(5.0); + mock.Distance(Any(), Any()).Returns(5.0); var origin = new Point { X = 0, Y = 0 }; var target = new Point { X = 3, Y = 4 }; @@ -152,7 +152,7 @@ public async Task In_Struct_Params() public async Task In_Struct_Mixed_With_Regular() { var mock = Mock.Of(); - mock.Contains(Arg.Any(), Arg.Any()).Returns(true); + mock.Contains(Any(), Any()).Returns(true); var center = new Point { X = 5, Y = 5 }; var result = mock.Object.Contains(center, 10); @@ -165,7 +165,7 @@ public async Task In_Params_Callback_With_Args() { int capturedA = 0, capturedB = 0; var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Callback((object?[] args) => { capturedA = (int)args[0]!; @@ -183,7 +183,7 @@ public async Task In_Params_Callback_With_Args() public async Task In_Params_Verify_Specific_Values() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(0); + mock.Add(Any(), Any()).Returns(0); mock.Object.Add(1, 2); mock.Object.Add(3, 4); @@ -199,7 +199,7 @@ public async Task In_String_Param_Matching() { var messages = new List(); var mock = Mock.Of(); - mock.Log(Arg.Any()).Callback((object?[] args) => messages.Add((string)args[0]!)); + mock.Log(Any()).Callback((object?[] args) => messages.Add((string)args[0]!)); mock.Object.Log("first"); mock.Object.Log("second"); @@ -213,7 +213,7 @@ public async Task In_String_Param_Matching() public async Task In_Params_Arg_Is_Predicate() { var mock = Mock.Of(); - mock.Add(Arg.Is(x => x > 0), Arg.Is(x => x > 0)).Returns(100); + mock.Add(Is(x => x > 0), Is(x => x > 0)).Returns(100); var r1 = mock.Object.Add(5, 10); await Assert.That(r1).IsEqualTo(100); diff --git a/TUnit.Mocks.Tests/InvocationsTests.cs b/TUnit.Mocks.Tests/InvocationsTests.cs index 0e5ae4d433..32827cf5fc 100644 --- a/TUnit.Mocks.Tests/InvocationsTests.cs +++ b/TUnit.Mocks.Tests/InvocationsTests.cs @@ -12,7 +12,7 @@ public interface IService public async Task Invocations_Returns_All_Calls() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("key1"); @@ -26,7 +26,7 @@ public async Task Invocations_Returns_All_Calls() public async Task Invocations_Contains_Correct_Method_Names() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("key1"); @@ -41,7 +41,7 @@ public async Task Invocations_Contains_Correct_Method_Names() public async Task Invocations_Contains_Correct_Arguments() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("hello"); @@ -60,7 +60,7 @@ public async Task Invocations_Is_Empty_When_No_Calls_Made() public async Task Invocations_Is_Empty_After_Reset() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("key1"); diff --git a/TUnit.Mocks.Tests/MatcherTests.cs b/TUnit.Mocks.Tests/MatcherTests.cs index 12c4932416..72a3bc5c20 100644 --- a/TUnit.Mocks.Tests/MatcherTests.cs +++ b/TUnit.Mocks.Tests/MatcherTests.cs @@ -13,7 +13,7 @@ public async Task InRange_MatchesWithinBounds() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.IsInRange(1, 10), Arg.Any()).Returns(99); + mock.Add(IsInRange(1, 10), Any()).Returns(99); // Act ICalculator calc = mock.Object; @@ -29,7 +29,7 @@ public async Task InRange_RejectsOutsideBounds() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.IsInRange(1, 10), Arg.Any()).Returns(99); + mock.Add(IsInRange(1, 10), Any()).Returns(99); // Act ICalculator calc = mock.Object; @@ -46,7 +46,7 @@ public async Task IsIn_MatchesSetMembers() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.IsIn(1, 3, 5), Arg.Any()).Returns(77); + mock.Add(IsIn(1, 3, 5), Any()).Returns(77); // Act ICalculator calc = mock.Object; @@ -62,7 +62,7 @@ public async Task IsIn_RejectsNonMembers() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.IsIn(1, 3, 5), Arg.Any()).Returns(77); + mock.Add(IsIn(1, 3, 5), Any()).Returns(77); // Act ICalculator calc = mock.Object; @@ -79,7 +79,7 @@ public async Task IsNotIn_MatchesNonMembers() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.IsNotIn(1, 3, 5), Arg.Any()).Returns(88); + mock.Add(IsNotIn(1, 3, 5), Any()).Returns(88); // Act ICalculator calc = mock.Object; @@ -96,7 +96,7 @@ public async Task IsNotIn_RejectsSetMembers() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.IsNotIn(1, 3, 5), Arg.Any()).Returns(88); + mock.Add(IsNotIn(1, 3, 5), Any()).Returns(88); // Act ICalculator calc = mock.Object; @@ -112,7 +112,7 @@ public async Task Not_NegatesInnerMatcher() { // Arrange — Not(Is(5)) should match everything except 5 var mock = Mock.Of(); - mock.Add(Arg.Not(Arg.Is(5)), Arg.Any()).Returns(66); + mock.Add(Not(Is(5)), Any()).Returns(66); // Act ICalculator calc = mock.Object; @@ -129,7 +129,7 @@ public async Task Not_WithPredicateMatcher() { // Arrange — Not(Is(x => x > 0)) should match non-positive values var mock = Mock.Of(); - mock.Add(Arg.Not(Arg.Is(x => x > 0)), Arg.Any()).Returns(55); + mock.Add(Not(Is(x => x > 0)), Any()).Returns(55); // Act ICalculator calc = mock.Object; diff --git a/TUnit.Mocks.Tests/MockRepositoryTests.cs b/TUnit.Mocks.Tests/MockRepositoryTests.cs index fc2025cf65..84bb2e7246 100644 --- a/TUnit.Mocks.Tests/MockRepositoryTests.cs +++ b/TUnit.Mocks.Tests/MockRepositoryTests.cs @@ -48,8 +48,8 @@ public async Task Repository_VerifyAll_Passes_When_All_Setups_Invoked() var serviceMock = repo.Of(); var loggerMock = repo.Of(); - serviceMock.GetData(Arg.Any()).Returns("result"); - loggerMock.Log(Arg.Any()); + serviceMock.GetData(Any()).Returns("result"); + loggerMock.Log(Any()); // Act — invoke all setups serviceMock.Object.GetData(1); @@ -67,8 +67,8 @@ public async Task Repository_VerifyAll_Throws_When_Setup_Not_Invoked() var serviceMock = repo.Of(); var loggerMock = repo.Of(); - serviceMock.GetData(Arg.Any()).Returns("result"); - loggerMock.Log(Arg.Any()); + serviceMock.GetData(Any()).Returns("result"); + loggerMock.Log(Any()); // Act — only invoke one mock's setup serviceMock.Object.GetData(1); @@ -93,8 +93,8 @@ public async Task Repository_VerifyNoOtherCalls_Passes_When_All_Verified() loggerMock.Object.Log("hello"); // Verify each call - serviceMock.GetData(Arg.Is(1)).WasCalled(); - loggerMock.Log(Arg.Is("hello")).WasCalled(); + serviceMock.GetData(Is(1)).WasCalled(); + loggerMock.Log(Is("hello")).WasCalled(); // Assert — no unverified calls repo.VerifyNoOtherCalls(); @@ -113,7 +113,7 @@ public async Task Repository_VerifyNoOtherCalls_Throws_When_Unverified_Calls_Exi loggerMock.Object.Log("hello"); // Only verify one mock - serviceMock.GetData(Arg.Is(1)).WasCalled(); + serviceMock.GetData(Is(1)).WasCalled(); // Assert — loggerMock has unverified calls var ex = Assert.Throws(() => repo.VerifyNoOtherCalls()); @@ -129,7 +129,7 @@ public async Task Repository_Reset_Clears_All_Mocks() var serviceMock = repo.Of(); var loggerMock = repo.Of(); - serviceMock.GetData(Arg.Any()).Returns("configured"); + serviceMock.GetData(Any()).Returns("configured"); loggerMock.Object.Log("call before reset"); // Act @@ -223,7 +223,7 @@ public async Task Repository_OfPartial_Configured_Method_Returns_Mock_Value() // Arrange var repo = new MockRepository(); var mock = repo.OfPartial(); - mock.Greet(Arg.Any()).Returns("Mocked!"); + mock.Greet(Any()).Returns("Mocked!"); // Act var result = mock.Object.Greet("World"); @@ -238,7 +238,7 @@ public async Task Repository_OfPartial_With_Constructor_Args() // Arrange var repo = new MockRepository(); var mock = repo.OfPartial("PREFIX"); - mock.Format(Arg.Any()).Returns("formatted"); + mock.Format(Any()).Returns("formatted"); // Act — GetPrefix is virtual, unconfigured → calls base var prefix = mock.Object.GetPrefix(); @@ -255,8 +255,8 @@ public async Task Repository_OfPartial_VerifyAll_Includes_Partial_Mocks() var serviceMock = repo.Of(); var partialMock = repo.OfPartial(); - serviceMock.GetData(Arg.Any()).Returns("data"); - partialMock.Greet(Arg.Any()).Returns("Hi"); + serviceMock.GetData(Any()).Returns("data"); + partialMock.Greet(Any()).Returns("Hi"); // Act — invoke both setups serviceMock.Object.GetData(1); diff --git a/TUnit.Mocks.Tests/MultipleInterfaceTests.cs b/TUnit.Mocks.Tests/MultipleInterfaceTests.cs index 3cadd68826..6bc00ba35d 100644 --- a/TUnit.Mocks.Tests/MultipleInterfaceTests.cs +++ b/TUnit.Mocks.Tests/MultipleInterfaceTests.cs @@ -52,14 +52,14 @@ public async Task Can_Setup_Methods_From_Primary_Interface() { // Arrange var mock = Mock.Of(); - mock.Log(Arg.Any()); + mock.Log(Any()); // Act var logger = mock.Object; logger.Log("test message"); // Assert — call recorded and verifiable - mock.Log(Arg.Is("test message")).WasCalled(); + mock.Log(Is("test message")).WasCalled(); } [Test] @@ -107,7 +107,7 @@ public async Task Multi_Mock_Shares_Single_Engine() { // Arrange var mock = Mock.Of(); - mock.Log(Arg.Any()); + mock.Log(Any()); // Act — call primary and secondary interface methods mock.Object.Log("hello"); @@ -135,7 +135,7 @@ public async Task Mock_Of_Four_Interfaces_Can_Use_Primary() { // Arrange var mock = Mock.Of(); - mock.Log(Arg.Any()); + mock.Log(Any()); // Act mock.Object.Log("hello from four-interface mock"); diff --git a/TUnit.Mocks.Tests/OutRefAssignmentTests.cs b/TUnit.Mocks.Tests/OutRefAssignmentTests.cs index a8a9eb93e1..f75d741b32 100644 --- a/TUnit.Mocks.Tests/OutRefAssignmentTests.cs +++ b/TUnit.Mocks.Tests/OutRefAssignmentTests.cs @@ -49,7 +49,7 @@ public async Task Out_Parameter_Int_Can_Be_Set_Via_Typed_Api() { // Arrange var mock = Mock.Of(); - mock.TryParse(Arg.Any()) + mock.TryParse(Any()) .Returns(true) .SetsOutResult(42); @@ -68,7 +68,7 @@ public async Task Out_Parameter_With_Any_Matcher_Via_Typed_Api() { // Arrange var mock = Mock.Of(); - mock.TryGet(Arg.Any()) + mock.TryGet(Any()) .Returns(true) .SetsOutValue("any-value"); @@ -88,7 +88,7 @@ public async Task Ref_Parameter_Can_Be_Set_Via_Typed_Api() { // Arrange var mock = Mock.Of(); - mock.Swap(Arg.Any()) + mock.Swap(Any()) .SetsRefValue(99); IDictionary dict = mock.Object; @@ -125,7 +125,7 @@ public async Task Typed_And_Chaining_Works() { // Arrange — chain typed out/ref setter with Returns var mock = Mock.Of(); - mock.TryGet(Arg.Any()) + mock.TryGet(Any()) .SetsOutValue("chained") .Returns(true); diff --git a/TUnit.Mocks.Tests/OutRefTests.cs b/TUnit.Mocks.Tests/OutRefTests.cs index 24abcd2bfb..a2b3ff1967 100644 --- a/TUnit.Mocks.Tests/OutRefTests.cs +++ b/TUnit.Mocks.Tests/OutRefTests.cs @@ -67,7 +67,7 @@ public async Task Out_Parameter_Method_With_Any_Matcher() { // Arrange var mock = Mock.Of(); - mock.TryGet(Arg.Any()).Returns(true); + mock.TryGet(Any()).Returns(true); // Act IDictionary dict = mock.Object; diff --git a/TUnit.Mocks.Tests/PartialMockTests.cs b/TUnit.Mocks.Tests/PartialMockTests.cs index 0ed6d333e1..ed3e509753 100644 --- a/TUnit.Mocks.Tests/PartialMockTests.cs +++ b/TUnit.Mocks.Tests/PartialMockTests.cs @@ -86,7 +86,7 @@ public async Task Configured_Virtual_Method_Returns_Override_Instead_Of_Base() // Arrange var mock = Mock.OfPartial(); mock.GetName().Returns("TestName"); - mock.Calculate(Arg.Any()).Returns(42); + mock.Calculate(Any()).Returns(42); // Act var result = mock.Object.Calculate(5); @@ -113,7 +113,7 @@ public async Task Concrete_Class_Override_Returns_Configured_Value() { // Arrange var mock = Mock.OfPartial(); - mock.Greet(Arg.Any()).Returns("Mocked!"); + mock.Greet(Any()).Returns("Mocked!"); // Act var result = mock.Object.Greet("World"); @@ -144,7 +144,7 @@ public async Task Constructor_Args_Passed_To_Base() { // Arrange var mock = Mock.OfPartial("PREFIX"); - mock.Format(Arg.Any()).Returns("formatted"); + mock.Format(Any()).Returns("formatted"); // Act - GetPrefix is virtual and unconfigured, so calls base which uses _prefix var prefix = mock.Object.GetPrefix(); @@ -186,14 +186,14 @@ public void Verify_Calls_On_Partial_Mock() { // Arrange var mock = Mock.OfPartial(); - mock.Greet(Arg.Any()).Returns("Hi"); + mock.Greet(Any()).Returns("Hi"); // Act mock.Object.Greet("Alice"); mock.Object.Greet("Bob"); // Assert - mock.Greet(Arg.Any()).WasCalled(Times.Exactly(2)); + mock.Greet(Any()).WasCalled(Times.Exactly(2)); } [Test] @@ -207,7 +207,7 @@ public void Verify_Calls_On_Unconfigured_Virtual_Method() mock.Object.Add(3, 4); // Assert - calls should still be recorded even when calling base - mock.Add(Arg.Any(), Arg.Any()).WasCalled(Times.Exactly(2)); + mock.Add(Any(), Any()).WasCalled(Times.Exactly(2)); } [Test] diff --git a/TUnit.Mocks.Tests/PropertyTests.cs b/TUnit.Mocks.Tests/PropertyTests.cs index 959d63a901..00f559cb28 100644 --- a/TUnit.Mocks.Tests/PropertyTests.cs +++ b/TUnit.Mocks.Tests/PropertyTests.cs @@ -194,7 +194,7 @@ public async Task Setter_Setup_Callback() var mock = Mock.Of(); var callbackCalled = false; Action callback = () => callbackCalled = true; - mock.Count.Set(Arg.Any()).Callback(callback); + mock.Count.Set(Any()).Callback(callback); // Act IPropertyService svc = mock.Object; diff --git a/TUnit.Mocks.Tests/ProtectedMemberTests.cs b/TUnit.Mocks.Tests/ProtectedMemberTests.cs index 52f6120882..1a2ea35956 100644 --- a/TUnit.Mocks.Tests/ProtectedMemberTests.cs +++ b/TUnit.Mocks.Tests/ProtectedMemberTests.cs @@ -34,13 +34,13 @@ public async Task Protected_Virtual_Method_Calls_Base_When_Not_Configured() // Arrange var mock = Mock.OfPartial(); mock.GetName().Returns("Test"); - mock.FormatResult(Arg.Any()).Returns("formatted"); + mock.FormatResult(Any()).Returns("formatted"); // Act — ComputeValue is protected virtual, not configured → calls base (input * 2) var result = mock.Object.ProcessAndFormat(5); // Assert — FormatResult receives 10 (5 * 2 from base ComputeValue) - mock.FormatResult(Arg.Is(10)).WasCalled(); + mock.FormatResult(Is(10)).WasCalled(); await Assert.That(result).IsEqualTo("formatted"); } @@ -50,14 +50,14 @@ public async Task Protected_Virtual_Method_Can_Be_Configured() // Arrange var mock = Mock.OfPartial(); mock.GetName().Returns("Test"); - mock.ComputeValue(Arg.Any()).Returns(42); - mock.FormatResult(Arg.Any()).Returns("configured"); + mock.ComputeValue(Any()).Returns(42); + mock.FormatResult(Any()).Returns("configured"); // Act — ComputeValue is configured to return 42 var result = mock.Object.ProcessAndFormat(5); // Assert — FormatResult receives 42 (from configured ComputeValue) - mock.FormatResult(Arg.Is(42)).WasCalled(); + mock.FormatResult(Is(42)).WasCalled(); await Assert.That(result).IsEqualTo("configured"); } @@ -67,7 +67,7 @@ public async Task Protected_Abstract_Method_Can_Be_Configured() // Arrange var mock = Mock.OfPartial(); mock.GetName().Returns("Test"); - mock.FormatResult(Arg.Any()).Returns("custom format"); + mock.FormatResult(Any()).Returns("custom format"); // Act var result = mock.Object.ProcessAndFormat(3); @@ -82,7 +82,7 @@ public async Task Protected_Method_Calls_Are_Recorded_In_Invocations() // Arrange var mock = Mock.OfPartial(); mock.GetName().Returns("Test"); - mock.FormatResult(Arg.Any()).Returns("result"); + mock.FormatResult(Any()).Returns("result"); // Act mock.Object.ProcessAndFormat(7); @@ -97,13 +97,13 @@ public async Task Protected_Method_Can_Be_Verified() // Arrange var mock = Mock.OfPartial(); mock.GetName().Returns("Test"); - mock.FormatResult(Arg.Any()).Returns("result"); + mock.FormatResult(Any()).Returns("result"); // Act mock.Object.ProcessAndFormat(5); // Assert — verify protected methods were called - mock.ComputeValue(Arg.Is(5)).WasCalled(); - mock.FormatResult(Arg.Any()).WasCalled(); + mock.ComputeValue(Is(5)).WasCalled(); + mock.FormatResult(Any()).WasCalled(); } } diff --git a/TUnit.Mocks.Tests/RealWorldScenarioTests.cs b/TUnit.Mocks.Tests/RealWorldScenarioTests.cs index 60f3c6818d..e55dab8e32 100644 --- a/TUnit.Mocks.Tests/RealWorldScenarioTests.cs +++ b/TUnit.Mocks.Tests/RealWorldScenarioTests.cs @@ -149,7 +149,7 @@ public async Task Repository_CRUD_Full_Lifecycle() var mock = Mock.Of(); var user = new UserDto { Id = 1, Name = "Alice", Email = "alice@example.com" }; - mock.CreateAsync(Arg.Any()).Returns(user); + mock.CreateAsync(Any()).Returns(user); mock.GetByIdAsync(1).Returns(user); mock.ExistsAsync(1).Returns(true); @@ -173,9 +173,9 @@ public async Task Repository_CRUD_Full_Lifecycle() await Assert.That(exists).IsTrue(); // Verify all calls - mock.CreateAsync(Arg.Any()).WasCalled(Times.Once); + mock.CreateAsync(Any()).WasCalled(Times.Once); mock.GetByIdAsync(1).WasCalled(Times.Once); - mock.UpdateAsync(Arg.Any()).WasCalled(Times.Once); + mock.UpdateAsync(Any()).WasCalled(Times.Once); mock.ExistsAsync(1).WasCalled(Times.Once); } @@ -189,7 +189,7 @@ public async Task Repository_With_CancellationToken_Default_Parameter() new() { Id = 1, Name = "Alice Smith", Email = "alice@example.com" }, new() { Id = 2, Name = "Alice Jones", Email = "alicej@example.com" }, }; - mock.FindByNameAsync("Alice", Arg.Any()) + mock.FindByNameAsync("Alice", Any()) .Returns((IReadOnlyList)users); IUserRepository repo = mock.Object; @@ -202,7 +202,7 @@ public async Task Repository_With_CancellationToken_Default_Parameter() await Assert.That(result[0].Name).IsEqualTo("Alice Smith"); // Verify - mock.FindByNameAsync("Alice", Arg.Any()).WasCalled(Times.Once); + mock.FindByNameAsync("Alice", Any()).WasCalled(Times.Once); } [Test] @@ -222,7 +222,7 @@ public async Task UnitOfWork_Transaction_Commit_Flow() // Assert mockUow.BeginTransactionAsync().WasCalled(Times.Once); - mockUow.SaveChangesAsync(Arg.Any()).WasCalled(Times.Once); + mockUow.SaveChangesAsync(Any()).WasCalled(Times.Once); mockTx.CommitAsync().WasCalled(Times.Once); mockTx.RollbackAsync().WasNeverCalled(); } @@ -234,7 +234,7 @@ public async Task UnitOfWork_Transaction_Rollback_On_Exception() var mockTx = Mock.Of(); var mockUow = Mock.Of(); mockUow.BeginTransactionAsync().Returns(mockTx.Object); - mockUow.SaveChangesAsync(Arg.Any()) + mockUow.SaveChangesAsync(Any()) .Throws(); IUnitOfWork uow = mockUow.Object; @@ -310,9 +310,9 @@ public async Task Logger_Method_Overloads_Distinct_Setups() var twoArgCallCount = 0; var threeArgCallCount = 0; - mock.Log(Arg.Any(), Arg.Any()) + mock.Log(Any(), Any()) .Callback(() => twoArgCallCount++); - mock.Log(Arg.Any(), Arg.Any(), Arg.Any()) + mock.Log(Any(), Any(), Any()) .Callback(() => threeArgCallCount++); ILogger logger = mock.Object; @@ -326,8 +326,8 @@ public async Task Logger_Method_Overloads_Distinct_Setups() await Assert.That(threeArgCallCount).IsEqualTo(1); // Verify - mock.Log(Arg.Any(), Arg.Any()).WasCalled(Times.Once); - mock.Log(Arg.Any(), Arg.Any(), Arg.Any()).WasCalled(Times.Once); + mock.Log(Any(), Any()).WasCalled(Times.Once); + mock.Log(Any(), Any(), Any()).WasCalled(Times.Once); } [Test] @@ -363,8 +363,8 @@ public async Task Notification_Conditional_Send() { // Arrange — SendSmsAsync returns true for one number, false for another var mock = Mock.Of(); - mock.SendSmsAsync("+1234567890", Arg.Any()).Returns(true); - mock.SendSmsAsync("+0000000000", Arg.Any()).Returns(false); + mock.SendSmsAsync("+1234567890", Any()).Returns(true); + mock.SendSmsAsync("+0000000000", Any()).Returns(false); INotificationService notify = mock.Object; @@ -514,7 +514,7 @@ public async Task DI_Integration_User_Not_Found() // Verify email was NEVER sent mockNotify.SendEmailAsync( - Arg.Any(), Arg.Any(), Arg.Any() + Any(), Any(), Any() ).WasNeverCalled(); } @@ -526,13 +526,13 @@ public async Task DI_Integration_Verify_Exact_Email_Content() var mockNotify = Mock.Of(); var mockLogger = Mock.Of(); - var bodyArg = Arg.Any(); + var bodyArg = Any(); var user = new UserDto { Id = 7, Name = "Charlie", Email = "charlie@example.com" }; mockRepo.GetByIdAsync(7).Returns(user); // SendEmailAsync returns Task (void-async), so use Callback to capture args mockNotify.SendEmailAsync( - Arg.Any(), Arg.Any(), bodyArg + Any(), Any(), bodyArg ).Callback(() => { }); var service = new OrderService(mockRepo.Object, mockNotify.Object, mockLogger.Object); @@ -571,9 +571,9 @@ public async Task Nullable_Parameter_Matching() // Arrange var mock = Mock.Of(); var callCount = 0; - mock.Process(Arg.IsNull(), Arg.Any()) + mock.Process(IsNull(), Any()) .Callback(() => callCount++); - mock.Process(Arg.IsNotNull(), Arg.Any()) + mock.Process(IsNotNull(), Any()) .Callback(() => callCount += 10); INullableService svc = mock.Object; @@ -592,7 +592,7 @@ public async Task Nullable_Async_Return() { // Arrange var mock = Mock.Of(); - mock.FindUserAsync(Arg.Any()).Returns((UserDto?)null); + mock.FindUserAsync(Any()).Returns((UserDto?)null); INullableService svc = mock.Object; diff --git a/TUnit.Mocks.Tests/RefStructTests.cs b/TUnit.Mocks.Tests/RefStructTests.cs index 92fef06cb5..3fe98887d1 100644 --- a/TUnit.Mocks.Tests/RefStructTests.cs +++ b/TUnit.Mocks.Tests/RefStructTests.cs @@ -182,7 +182,7 @@ public async Task Mixed_Params_Verification_With_Matcher() // Assert — verify by the string destination (non-ref-struct param) mock.Send("server-a").WasCalled(Times.Exactly(2)); mock.Send("server-b").WasCalled(Times.Once); - mock.Send(Arg.Any()).WasCalled(Times.Exactly(3)); + mock.Send(Any()).WasCalled(Times.Exactly(3)); await Assert.That(true).IsTrue(); } @@ -259,7 +259,7 @@ public async Task RefStructArg_Mixed_Verification() // Assert — verify with both Arg and RefStructArg> mock.Send("server-a", RefStructArg>.Any).WasCalled(Times.Once); - mock.Send(Arg.Any(), RefStructArg>.Any).WasCalled(Times.Exactly(2)); + mock.Send(Any(), RefStructArg>.Any).WasCalled(Times.Exactly(2)); await Assert.That(true).IsTrue(); } @@ -348,7 +348,7 @@ public async Task RefStructArg_Mixed_Params_Verification_With_Matcher() // Assert — verify by the string destination (non-ref-struct param) with RefStructArg.Any mock.Send("server-a", RefStructArg>.Any).WasCalled(Times.Exactly(2)); mock.Send("server-b", RefStructArg>.Any).WasCalled(Times.Once); - mock.Send(Arg.Any(), RefStructArg>.Any).WasCalled(Times.Exactly(3)); + mock.Send(Any(), RefStructArg>.Any).WasCalled(Times.Exactly(3)); await Assert.That(true).IsTrue(); } diff --git a/TUnit.Mocks.Tests/RegexMatcherTests.cs b/TUnit.Mocks.Tests/RegexMatcherTests.cs index 896ffcec69..3da93cc8b3 100644 --- a/TUnit.Mocks.Tests/RegexMatcherTests.cs +++ b/TUnit.Mocks.Tests/RegexMatcherTests.cs @@ -13,7 +13,7 @@ public async Task Arg_Matches_With_Pattern_Matches_Matching_Strings() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Matches(@"^[A-Z]")).Returns("capitalized"); + mock.Greet(Matches(@"^[A-Z]")).Returns("capitalized"); // Act var greeter = mock.Object; @@ -28,7 +28,7 @@ public async Task Arg_Matches_With_Pattern_Does_Not_Match_NonMatching() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Matches(@"^[A-Z]")).Returns("capitalized"); + mock.Greet(Matches(@"^[A-Z]")).Returns("capitalized"); // Act var greeter = mock.Object; @@ -44,7 +44,7 @@ public async Task Arg_Matches_With_Regex_Object() // Arrange var regex = new Regex(@"\d{3}-\d{4}", RegexOptions.Compiled); var mock = Mock.Of(); - mock.Greet(Arg.Matches(regex)).Returns("phone"); + mock.Greet(Matches(regex)).Returns("phone"); // Act var greeter = mock.Object; @@ -59,7 +59,7 @@ public async Task Arg_Matches_Regex_Does_Not_Match_Null() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Matches(".*")).Returns("matched"); + mock.Greet(Matches(".*")).Returns("matched"); // Act var greeter = mock.Object; @@ -73,7 +73,7 @@ public async Task Arg_Matches_Regex_With_Email_Pattern() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Matches(@"^[\w.+-]+@[\w-]+\.[\w.]+$")).Returns("email"); + mock.Greet(Matches(@"^[\w.+-]+@[\w-]+\.[\w.]+$")).Returns("email"); // Act var greeter = mock.Object; diff --git a/TUnit.Mocks.Tests/ResetTests.cs b/TUnit.Mocks.Tests/ResetTests.cs index 470c065404..cecd601375 100644 --- a/TUnit.Mocks.Tests/ResetTests.cs +++ b/TUnit.Mocks.Tests/ResetTests.cs @@ -41,13 +41,13 @@ public async Task Reset_Clears_Call_History() calc.Add(5, 6); // Verify calls were recorded - mock.Add(Arg.Any(), Arg.Any()).WasCalled(Times.Exactly(3)); + mock.Add(Any(), Any()).WasCalled(Times.Exactly(3)); // Act Mock.Reset(mock); // Assert — after reset, call history is cleared - mock.Add(Arg.Any(), Arg.Any()).WasNeverCalled(); + mock.Add(Any(), Any()).WasNeverCalled(); await Assert.That(true).IsTrue(); } @@ -141,13 +141,13 @@ public async Task Reset_Clears_Void_Method_Call_History() calc.Log("message1"); calc.Log("message2"); - mock.Log(Arg.Any()).WasCalled(Times.Exactly(2)); + mock.Log(Any()).WasCalled(Times.Exactly(2)); // Act Mock.Reset(mock); // Assert — void method call history is cleared - mock.Log(Arg.Any()).WasNeverCalled(); + mock.Log(Any()).WasNeverCalled(); await Assert.That(true).IsTrue(); } diff --git a/TUnit.Mocks.Tests/SequentialBehaviorTests.cs b/TUnit.Mocks.Tests/SequentialBehaviorTests.cs index d8206c3e2b..db6d2ffc81 100644 --- a/TUnit.Mocks.Tests/SequentialBehaviorTests.cs +++ b/TUnit.Mocks.Tests/SequentialBehaviorTests.cs @@ -13,7 +13,7 @@ public async Task Throws_Then_Returns_First_Call_Throws_Second_Returns() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Throws() .Then() .Returns(5); @@ -34,7 +34,7 @@ public async Task ReturnsSequentially_Returns_Values_In_Order() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .ReturnsSequentially(1, 2, 3); ICalculator calc = mock.Object; @@ -50,7 +50,7 @@ public async Task ReturnsSequentially_Last_Value_Repeats() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .ReturnsSequentially(10, 20); ICalculator calc = mock.Object; @@ -68,7 +68,7 @@ public async Task Void_Method_Callback_Then_Throws() // Arrange var callbackInvoked = false; var mock = Mock.Of(); - mock.Log(Arg.Any()) + mock.Log(Any()) .Callback(() => callbackInvoked = true) .Then() .Throws(); @@ -109,7 +109,7 @@ public async Task Chained_Returns_With_Then() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Any()) + mock.Greet(Any()) .Returns("first") .Then() .Returns("second") diff --git a/TUnit.Mocks.Tests/SpanReturnTests.cs b/TUnit.Mocks.Tests/SpanReturnTests.cs index e876498b80..5bb0429f61 100644 --- a/TUnit.Mocks.Tests/SpanReturnTests.cs +++ b/TUnit.Mocks.Tests/SpanReturnTests.cs @@ -93,7 +93,7 @@ public async Task Returns_ReadOnlySpan_With_Arg_Matching() public async Task Returns_ReadOnlySpan_With_Arg_Any() { var mock = Mock.Of(); - mock.GetBytes(Arg.Any()).Returns(new ReadOnlySpan([0xFF])); + mock.GetBytes(Any()).Returns(new ReadOnlySpan([0xFF])); var result = mock.Object.GetBytes("anything"); var len = result.Length; @@ -187,14 +187,14 @@ public async Task Span_Return_Verify_WasNeverCalled() public async Task Span_Return_Verify_With_Specific_Args() { var mock = Mock.Of(); - mock.GetBytes(Arg.Any()).Returns(new ReadOnlySpan([1])); + mock.GetBytes(Any()).Returns(new ReadOnlySpan([1])); mock.Object.GetBytes("hello"); mock.Object.GetBytes("world"); mock.GetBytes("hello").WasCalled(Times.Once); mock.GetBytes("world").WasCalled(Times.Once); - mock.GetBytes(Arg.Any()).WasCalled(Times.Exactly(2)); + mock.GetBytes(Any()).WasCalled(Times.Exactly(2)); await Assert.That(true).IsTrue(); } diff --git a/TUnit.Mocks.Tests/ThreadSafetyTests.cs b/TUnit.Mocks.Tests/ThreadSafetyTests.cs index 9607b4f629..c59804d181 100644 --- a/TUnit.Mocks.Tests/ThreadSafetyTests.cs +++ b/TUnit.Mocks.Tests/ThreadSafetyTests.cs @@ -13,7 +13,7 @@ public async Task Concurrent_Calls_Are_Thread_Safe() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; // Act — 100 concurrent calls @@ -40,7 +40,7 @@ public async Task Concurrent_Void_Calls_Are_Thread_Safe() await Task.WhenAll(tasks); // Assert — all 100 calls should be recorded - mock.Log(Arg.Any()).WasCalled(Times.Exactly(100)); + mock.Log(Any()).WasCalled(Times.Exactly(100)); await Assert.That(true).IsTrue(); } @@ -56,7 +56,7 @@ public async Task Concurrent_Setup_And_Call() { for (int i = 0; i < 50; i++) { - mock.Add(Arg.Any(), Arg.Any()).Returns(i); + mock.Add(Any(), Any()).Returns(i); } }); @@ -130,11 +130,11 @@ public async Task Concurrent_Calls_On_Different_Interfaces() { // Arrange var calcMock = Mock.Of(); - calcMock.Add(Arg.Any(), Arg.Any()).Returns(99); + calcMock.Add(Any(), Any()).Returns(99); ICalculator calc = calcMock.Object; var greeterMock = Mock.Of(); - greeterMock.Greet(Arg.Any()).Returns("hi"); + greeterMock.Greet(Any()).Returns("hi"); IGreeter greeter = greeterMock.Object; // Act — concurrent calls on both mocks @@ -164,7 +164,7 @@ public async Task Concurrent_Calls_All_Recorded_In_History() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Any()).Returns("hello"); + mock.Greet(Any()).Returns("hello"); IGreeter greeter = mock.Object; // Act — 100 concurrent calls @@ -173,7 +173,7 @@ public async Task Concurrent_Calls_All_Recorded_In_History() await Task.WhenAll(tasks); // Assert — verify total call count - mock.Greet(Arg.Any()).WasCalled(Times.Exactly(100)); + mock.Greet(Any()).WasCalled(Times.Exactly(100)); await Assert.That(true).IsTrue(); } } diff --git a/TUnit.Mocks.Tests/TypedCallbackTests.cs b/TUnit.Mocks.Tests/TypedCallbackTests.cs index 467fd6ee86..1b229f7fa3 100644 --- a/TUnit.Mocks.Tests/TypedCallbackTests.cs +++ b/TUnit.Mocks.Tests/TypedCallbackTests.cs @@ -14,7 +14,7 @@ public async Task Callback_With_Args_Receives_Arguments() // Arrange object?[]? capturedArgs = null; var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Callback((Action)(args => capturedArgs = args)); ICalculator calc = mock.Object; @@ -35,7 +35,7 @@ public async Task Callback_With_Args_On_Void_Method() // Arrange object?[]? capturedArgs = null; var mock = Mock.Of(); - mock.Log(Arg.Any()) + mock.Log(Any()) .Callback((Action)(args => capturedArgs = args)); ICalculator calc = mock.Object; @@ -54,7 +54,7 @@ public async Task Returns_With_Args_Computes_From_Arguments() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Returns((Func)(args => (int)args[0]! + (int)args[1]!)); ICalculator calc = mock.Object; @@ -70,7 +70,7 @@ public async Task Returns_With_Args_String_Concatenation() { // Arrange var mock = Mock.Of(); - mock.Greet(Arg.Any()) + mock.Greet(Any()) .Returns((Func)(args => $"Hello, {args[0]}!")); IGreeter greeter = mock.Object; @@ -85,7 +85,7 @@ public async Task Computed_Throw_With_Args() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Throws((Func)(args => new ArgumentException($"Bad args: {args[0]}, {args[1]}"))); @@ -101,7 +101,7 @@ public async Task Computed_Throw_On_Void_Method() { // Arrange var mock = Mock.Of(); - mock.Log(Arg.Any()) + mock.Log(Any()) .Throws((Func)(args => new InvalidOperationException($"Cannot log: {args[0]}"))); @@ -118,7 +118,7 @@ public async Task Callback_With_Args_Then_Returns() // Arrange object?[]? capturedArgs = null; var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Callback((Action)(args => capturedArgs = args)) .Then() .Returns(42); @@ -140,7 +140,7 @@ public async Task Returns_With_Args_Repeats_On_Subsequent_Calls() { // Arrange var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Returns((Func)(args => (int)args[0]! * (int)args[1]!)); ICalculator calc = mock.Object; @@ -157,7 +157,7 @@ public async Task Returns_With_Args_Repeats_On_Subsequent_Calls() public async Task StronglyTyped_Returns_Computes_From_Arguments() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns((int a, int b) => a + b); + mock.Add(Any(), Any()).Returns((int a, int b) => a + b); ICalculator calc = mock.Object; @@ -172,7 +172,7 @@ public async Task StronglyTyped_Callback_Receives_Arguments() var capturedArgs = new List<(int a, int b)>(); // Callback is a behavior — first call runs callback, second call runs Returns - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Callback((int a, int b) => capturedArgs.Add((a, b))); ICalculator calc = mock.Object; @@ -189,7 +189,7 @@ public async Task StronglyTyped_Callback_Receives_Arguments() public async Task StronglyTyped_Throws_With_Argument_Dependent_Exception() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Throws((int a, int b) => new ArgumentException($"Cannot add {a} and {b}")); ICalculator calc = mock.Object; @@ -204,7 +204,7 @@ public async Task StronglyTyped_Void_Callback() var mock = Mock.Of(); string? capturedMessage = null; - mock.Log(Arg.Any()) + mock.Log(Any()) .Callback((string msg) => capturedMessage = msg); ICalculator calc = mock.Object; @@ -217,7 +217,7 @@ public async Task StronglyTyped_Void_Callback() public async Task StronglyTyped_Void_Throws() { var mock = Mock.Of(); - mock.Log(Arg.Any()) + mock.Log(Any()) .Throws((string msg) => new InvalidOperationException($"Cannot log: {msg}")); ICalculator calc = mock.Object; @@ -230,7 +230,7 @@ public async Task StronglyTyped_Void_Throws() public async Task StronglyTyped_Returns_Single_Parameter() { var mock = Mock.Of(); - mock.Greet(Arg.Any()).Returns((string name) => $"Hello, {name}!"); + mock.Greet(Any()).Returns((string name) => $"Hello, {name}!"); IGreeter greeter = mock.Object; @@ -241,7 +241,7 @@ public async Task StronglyTyped_Returns_Single_Parameter() public async Task StronglyTyped_With_Then_Chain() { var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Returns((int a, int b) => a + b) .Then() .Returns(99); @@ -257,7 +257,7 @@ public async Task StronglyTyped_Existing_Untyped_Returns_Still_Works() { // Verify that existing untyped API still compiles and works var mock = Mock.Of(); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); ICalculator calc = mock.Object; @@ -269,7 +269,7 @@ public async Task StronglyTyped_Existing_Untyped_Callback_Still_Works() { var mock = Mock.Of(); var called = false; - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Callback(() => called = true) .Then() .Returns(0); diff --git a/TUnit.Mocks.Tests/UntypedAnyTests.cs b/TUnit.Mocks.Tests/UntypedAnyTests.cs new file mode 100644 index 0000000000..9adcacd343 --- /dev/null +++ b/TUnit.Mocks.Tests/UntypedAnyTests.cs @@ -0,0 +1,347 @@ +using TUnit.Mocks.Arguments; + +namespace TUnit.Mocks.Tests; + +/// +/// Tests for the untyped Any() method and its implicit conversion to Arg{T}. +/// The non-generic Any() returns an which implicitly converts to +/// Arg{T} via public static implicit operator Arg{T}(Arg arg), creating +/// an AnyMatcher{T} under the hood. +/// +public class UntypedAnyTests +{ + // ────────────────────────────────────────────── + // Implicit conversion to value types + // ────────────────────────────────────────────── + + [Test] + public async Task Any_Converts_To_Int_And_Matches_All() + { + var mock = Mock.Of(); + mock.Add(Any(), Any()).Returns(42); + + await Assert.That(mock.Object.Add(0, 0)).IsEqualTo(42); + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(42); + await Assert.That(mock.Object.Add(-100, int.MaxValue)).IsEqualTo(42); + await Assert.That(mock.Object.Add(int.MinValue, int.MaxValue)).IsEqualTo(42); + } + + [Test] + public async Task Any_Converts_To_Bool() + { + var mock = Mock.Of(); + mock.Process(Any(), Any()).Callback(() => { }); + + // Should not throw — Any() converts to Arg + mock.Object.Process("data", true); + mock.Object.Process("data", false); + + mock.Process(Any(), Any()).WasCalled(Times.Exactly(2)); + } + + [Test] + public async Task Any_Converts_To_Double() + { + var mock = Mock.Of(); + mock.Format(Any(), Any()).Returns("ok"); + + await Assert.That(mock.Object.Format(3.14, 2)).IsEqualTo("ok"); + await Assert.That(mock.Object.Format(0.0, 0)).IsEqualTo("ok"); + await Assert.That(mock.Object.Format(-1.5, 10)).IsEqualTo("ok"); + } + + [Test] + public async Task Any_Converts_To_Enum() + { + var mock = Mock.Of(); + mock.CountByStatusAsync(Any()).Returns(99); + + await Assert.That(await mock.Object.CountByStatusAsync(Status.Active)).IsEqualTo(99); + await Assert.That(await mock.Object.CountByStatusAsync(Status.Pending)).IsEqualTo(99); + await Assert.That(await mock.Object.CountByStatusAsync(Status.Completed)).IsEqualTo(99); + await Assert.That(await mock.Object.CountByStatusAsync(Status.Failed)).IsEqualTo(99); + } + + // ────────────────────────────────────────────── + // Implicit conversion to reference types + // ────────────────────────────────────────────── + + [Test] + public async Task Any_Converts_To_String() + { + var mock = Mock.Of(); + mock.Greet(Any()).Returns("hello"); + + await Assert.That(mock.Object.Greet("Alice")).IsEqualTo("hello"); + await Assert.That(mock.Object.Greet("")).IsEqualTo("hello"); + } + + [Test] + public async Task Any_Converts_To_String_And_Matches_Null() + { + var mock = Mock.Of(); + mock.Greet(Any()).Returns("matched"); + + await Assert.That(mock.Object.Greet(null!)).IsEqualTo("matched"); + } + + [Test] + public async Task Any_Converts_To_Array() + { + var mock = Mock.Of(); + mock.BuildQuery( + Any(), Any(), Any(), Any(), Any(), Any(), Any() + ).Returns("query"); + + var result = mock.Object.BuildQuery("users", ["id"], null, null, null, null, true); + + await Assert.That(result).IsEqualTo("query"); + } + + // ────────────────────────────────────────────── + // Implicit conversion to nullable types + // ────────────────────────────────────────────── + + [Test] + public async Task Any_Converts_To_Nullable_String() + { + var mock = Mock.Of(); + mock.Process(Any(), Any()).Callback(() => { }); + + // Both null and non-null should match + mock.Object.Process(null, null); + mock.Object.Process("text", 42); + + mock.Process(Any(), Any()).WasCalled(Times.Exactly(2)); + } + + [Test] + public async Task Any_Converts_To_Nullable_Int() + { + var mock = Mock.Of(); + mock.BuildQuery("t", Any(), Any(), Any(), Any(), Any(), Any()).Returns("ok"); + + // Nullable int? params (limit, offset) accept both null and values + await Assert.That(mock.Object.BuildQuery("t", [], null, null, null, null, true)).IsEqualTo("ok"); + await Assert.That(mock.Object.BuildQuery("t", [], null, 10, 0, null, true)).IsEqualTo("ok"); + } + + // ────────────────────────────────────────────── + // Mixed with typed matchers + // ────────────────────────────────────────────── + + [Test] + public async Task Any_Mixed_With_Exact_Value() + { + var mock = Mock.Of(); + mock.Add(Any(), 5).Returns(50); + + await Assert.That(mock.Object.Add(0, 5)).IsEqualTo(50); + await Assert.That(mock.Object.Add(999, 5)).IsEqualTo(50); + await Assert.That(mock.Object.Add(0, 6)).IsEqualTo(0); + } + + [Test] + public async Task Any_Mixed_With_Typed_Any() + { + var mock = Mock.Of(); + mock.Add(Any(), Any()).Returns(77); + + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(77); + await Assert.That(mock.Object.Add(-5, 0)).IsEqualTo(77); + } + + [Test] + public async Task Any_Mixed_With_Predicate_Matcher() + { + var mock = Mock.Of(); + mock.Add(Any(), Is(x => x > 0)).Returns(100); + + await Assert.That(mock.Object.Add(0, 1)).IsEqualTo(100); + await Assert.That(mock.Object.Add(0, 0)).IsEqualTo(0); + await Assert.That(mock.Object.Add(0, -1)).IsEqualTo(0); + } + + [Test] + public async Task Any_Mixed_With_IsNull_Matcher() + { + var mock = Mock.Of(); + mock.Greet(IsNull()).Returns("was null"); + mock.Greet(Any()).Returns("catch-all"); + + // Last setup wins — Any() catches everything including null + await Assert.That(mock.Object.Greet(null!)).IsEqualTo("catch-all"); + await Assert.That(mock.Object.Greet("hi")).IsEqualTo("catch-all"); + } + + // ────────────────────────────────────────────── + // Verification context + // ────────────────────────────────────────────── + + [Test] + public async Task Any_Works_In_Verification() + { + var mock = Mock.Of(); + mock.Add(Any(), Any()).Returns(1); + + mock.Object.Add(1, 2); + mock.Object.Add(3, 4); + mock.Object.Add(5, 6); + + mock.Add(Any(), Any()).WasCalled(Times.Exactly(3)); + } + + [Test] + public async Task Any_WasNeverCalled_Verification() + { + var mock = Mock.Of(); + + mock.Add(Any(), Any()).WasNeverCalled(); + } + + [Test] + public async Task Any_Verification_On_Void_Method() + { + var mock = Mock.Of(); + + mock.Object.Log("hello"); + mock.Object.Log("world"); + + mock.Log(Any()).WasCalled(Times.Exactly(2)); + } + + // ────────────────────────────────────────────── + // Setup behaviors (Returns, Throws, Callback) + // ────────────────────────────────────────────── + + [Test] + public async Task Any_With_Throws() + { + var mock = Mock.Of(); + mock.Add(Any(), Any()).Throws(); + + await Assert.That(() => mock.Object.Add(1, 2)).Throws(); + } + + [Test] + public async Task Any_With_Callback() + { + var callCount = 0; + var mock = Mock.Of(); + mock.Add(Any(), Any()) + .Callback(() => callCount++); + + mock.Object.Add(1, 2); + mock.Object.Add(3, 4); + + await Assert.That(callCount).IsEqualTo(2); + } + + [Test] + public async Task Any_With_Sequential_Returns() + { + var mock = Mock.Of(); + mock.Greet(Any()) + .Returns("first") + .Then() + .Returns("second"); + + await Assert.That(mock.Object.Greet("a")).IsEqualTo("first"); + await Assert.That(mock.Object.Greet("b")).IsEqualTo("second"); + await Assert.That(mock.Object.Greet("c")).IsEqualTo("second"); + } + + // ────────────────────────────────────────────── + // All params using Any() (many-arg method) + // ────────────────────────────────────────────── + + [Test] + public async Task Any_All_Seven_Params() + { + var mock = Mock.Of(); + mock.BuildQuery(Any(), Any(), Any(), Any(), Any(), Any(), Any()) + .Returns("wildcard"); + + var result = mock.Object.BuildQuery("orders", ["*"], "active", 100, 0, "date", false); + + await Assert.That(result).IsEqualTo("wildcard"); + } + + // ────────────────────────────────────────────── + // Async method support + // ────────────────────────────────────────────── + + [Test] + public async Task Any_With_Async_Method() + { + var mock = Mock.Of(); + mock.GetNameAsync(Any()).Returns("async-result"); + + var result = await mock.Object.GetNameAsync("key"); + + await Assert.That(result).IsEqualTo("async-result"); + } + + // ────────────────────────────────────────────── + // Capture requires typed Any() — not untyped + // ────────────────────────────────────────────── + + [Test] + public async Task Typed_Any_Captures_But_Untyped_Does_Not_Have_Values() + { + // Typed Any() supports capture + var typedArg = Any(); + var mock = Mock.Of(); + mock.Add(typedArg, Any()).Returns(1); + + mock.Object.Add(10, 0); + mock.Object.Add(20, 0); + + await Assert.That(typedArg.Values).Count().IsEqualTo(2); + await Assert.That(typedArg.Values[0]).IsEqualTo(10); + await Assert.That(typedArg.Values[1]).IsEqualTo(20); + await Assert.That(typedArg.Latest).IsEqualTo(20); + + // Untyped Any() is used for the second arg — still matches, no capture needed + mock.Add(Any(), Any()).WasCalled(Times.Exactly(2)); + } + + // ────────────────────────────────────────────── + // Overload disambiguation requires typed Any() + // ────────────────────────────────────────────── + + [Test] + public async Task Typed_Any_Disambiguates_Overloads() + { + var mock = Mock.Of(); + mock.Format(Any()).Returns("int-match"); + mock.Format(Any()).Returns("str-match"); + + await Assert.That(mock.Object.Format(42)).IsEqualTo("int-match"); + await Assert.That(mock.Object.Format("hello")).IsEqualTo("str-match"); + } + + // ────────────────────────────────────────────── + // Default values in loose mode with Any() + // ────────────────────────────────────────────── + + [Test] + public async Task Any_Without_Setup_Returns_Default_In_Loose_Mode() + { + var mock = Mock.Of(); + // No setup — loose mode returns defaults + + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(0); + await Assert.That(mock.Object.GetName()).IsNotNull(); + } + + [Test] + public async Task Any_In_Strict_Mode_Allows_Configured_Calls() + { + var mock = Mock.Of(MockBehavior.Strict); + mock.Add(Any(), Any()).Returns(42); + + await Assert.That(mock.Object.Add(1, 2)).IsEqualTo(42); + await Assert.That(mock.Object.Add(99, 0)).IsEqualTo(42); + } +} diff --git a/TUnit.Mocks.Tests/VerifyAllTests.cs b/TUnit.Mocks.Tests/VerifyAllTests.cs index 6d437ee99b..76aa8b8f11 100644 --- a/TUnit.Mocks.Tests/VerifyAllTests.cs +++ b/TUnit.Mocks.Tests/VerifyAllTests.cs @@ -14,8 +14,8 @@ public interface IService public async Task VerifyAll_Passes_When_All_Setups_Invoked() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); - mock.Process(Arg.Any()); + mock.GetValue(Any()).Returns("value"); + mock.Process(Any()); var svc = mock.Object; svc.GetValue("key"); @@ -29,8 +29,8 @@ public async Task VerifyAll_Passes_When_All_Setups_Invoked() public async Task VerifyAll_Fails_When_Setup_Not_Invoked() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); - mock.Process(Arg.Any()); + mock.GetValue(Any()).Returns("value"); + mock.Process(Any()); var svc = mock.Object; svc.GetValue("key"); // Only call GetValue, not Process @@ -43,7 +43,7 @@ public async Task VerifyAll_Fails_When_Setup_Not_Invoked() public async Task VerifyAll_Fails_When_No_Setups_Called() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var ex = Assert.Throws(() => Mock.VerifyAll(mock)); await Assert.That(ex.Message).Contains("GetValue"); diff --git a/TUnit.Mocks.Tests/VerifyNoOtherCallsTests.cs b/TUnit.Mocks.Tests/VerifyNoOtherCallsTests.cs index 8ec10cb614..537f547213 100644 --- a/TUnit.Mocks.Tests/VerifyNoOtherCallsTests.cs +++ b/TUnit.Mocks.Tests/VerifyNoOtherCallsTests.cs @@ -15,7 +15,7 @@ public interface IService public async Task VerifyNoOtherCalls_Passes_When_All_Calls_Verified() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("key1"); @@ -30,7 +30,7 @@ public async Task VerifyNoOtherCalls_Passes_When_All_Calls_Verified() public async Task VerifyNoOtherCalls_Fails_When_Unverified_Calls_Exist() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("key1"); @@ -55,7 +55,7 @@ public async Task VerifyNoOtherCalls_Passes_When_No_Calls_Made() public async Task VerifyNoOtherCalls_Works_After_Reset() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("key1"); @@ -70,7 +70,7 @@ public async Task VerifyNoOtherCalls_Works_After_Reset() public async Task VerifyNoOtherCalls_Multiple_Unverified_Shows_All() { var mock = Mock.Of(); - mock.GetValue(Arg.Any()).Returns("value"); + mock.GetValue(Any()).Returns("value"); var svc = mock.Object; svc.GetValue("a"); diff --git a/TUnit.Mocks.Tests/WrapRealObjectTests.cs b/TUnit.Mocks.Tests/WrapRealObjectTests.cs index 324c5886bc..c63a98f913 100644 --- a/TUnit.Mocks.Tests/WrapRealObjectTests.cs +++ b/TUnit.Mocks.Tests/WrapRealObjectTests.cs @@ -64,7 +64,7 @@ public async Task Configured_Call_Returns_Mock_Value_Instead_Of_Real() // Arrange var real = new RealCalculator(10); var mock = Mock.Wrap(real); - mock.Add(Arg.Any(), Arg.Any()).Returns(99); + mock.Add(Any(), Any()).Returns(99); // Act var result = mock.Object.Add(3, 4); @@ -111,7 +111,7 @@ public async Task String_Method_Can_Be_Overridden() // Arrange var real = new RealCalculator(42); var mock = Mock.Wrap(real); - mock.Describe(Arg.Any()).Returns("mocked description"); + mock.Describe(Any()).Returns("mocked description"); // Act var result = mock.Object.Describe("test"); @@ -131,7 +131,7 @@ public void Void_Method_Delegates_To_Real_Instance() mock.Object.Log("test message"); // Assert — call was recorded for verification - mock.Log(Arg.Any()).WasCalled(Times.Once); + mock.Log(Any()).WasCalled(Times.Once); } [Test] @@ -147,8 +147,8 @@ public void Verify_Records_Calls_Even_When_Delegating() mock.Object.Multiply(5, 6); // Assert — calls are still tracked - mock.Add(Arg.Any(), Arg.Any()).WasCalled(Times.Exactly(2)); - mock.Multiply(Arg.Any(), Arg.Any()).WasCalled(Times.Once); + mock.Add(Any(), Any()).WasCalled(Times.Exactly(2)); + mock.Multiply(Any(), Any()).WasCalled(Times.Once); } [Test] @@ -182,7 +182,7 @@ public async Task Wrap_Strict_Mode_Allows_Configured_Calls() // Arrange — strict mode with explicit setup var real = new RealCalculator(10); var mock = Mock.Wrap(MockBehavior.Strict, real); - mock.Add(Arg.Any(), Arg.Any()).Returns(42); + mock.Add(Any(), Any()).Returns(42); // Act var result = mock.Object.Add(2, 3); @@ -197,7 +197,7 @@ public async Task Wrap_Returns_Configured_Value() // Arrange var real = new RealCalculator(100); var mock = Mock.Wrap(real); - mock.Add(Arg.Any(), Arg.Any()).Returns(0); + mock.Add(Any(), Any()).Returns(0); // Act var result = mock.Object.Add(5, 7); @@ -212,7 +212,7 @@ public void Wrap_Throws_Works() // Arrange var real = new RealCalculator(); var mock = Mock.Wrap(real); - mock.Add(Arg.Any(), Arg.Any()) + mock.Add(Any(), Any()) .Throws(new InvalidOperationException("mock error")); // Act & Assert diff --git a/TUnit.Mocks/Arguments/Arg.cs b/TUnit.Mocks/Arguments/Arg.cs index 0f65197bf1..a3c60c70bf 100644 --- a/TUnit.Mocks/Arguments/Arg.cs +++ b/TUnit.Mocks/Arguments/Arg.cs @@ -7,8 +7,12 @@ namespace TUnit.Mocks.Arguments; /// Provides static factory methods for creating argument matchers /// used in mock setup and verification expressions. /// -public static class Arg +public class Arg { + private Arg() + { + } + /// Matches any value of the specified type, including null. public static Arg Any() => new(new AnyMatcher()); @@ -61,4 +65,6 @@ public static Arg IsInRange(T min, T max) where T : IComparable /// Negates the inner matcher -- matches when the inner matcher does NOT match. public static Arg Not(Arg inner) => new(new NotMatcher(inner.Matcher)); + + public static Arg Any() => new(); } diff --git a/TUnit.Mocks/Arguments/ArgOfT.cs b/TUnit.Mocks/Arguments/ArgOfT.cs index 756bfd299f..979974a2d0 100644 --- a/TUnit.Mocks/Arguments/ArgOfT.cs +++ b/TUnit.Mocks/Arguments/ArgOfT.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using TUnit.Mocks.Matchers; namespace TUnit.Mocks.Arguments; @@ -32,5 +32,11 @@ public Arg(IArgumentMatcher matcher) /// Implicitly converts a raw value to an using exact equality matching. /// The value to match against. - public static implicit operator Arg(T value) => new(new TUnit.Mocks.Matchers.ExactMatcher(value)); + public static implicit operator Arg(T value) => new(new ExactMatcher(value)); + + /// Implicitly converts a predicate to an using predicate matching. + /// The predicate that determines whether an argument matches. + public static implicit operator Arg(Func predicate) => new(new PredicateMatcher(predicate!)); + + public static implicit operator Arg(Arg arg) => new(new AnyMatcher()); } diff --git a/TUnit.Mocks/Arguments/RefStructArg.cs b/TUnit.Mocks/Arguments/RefStructArg.cs index 8abc5e54f9..da396c6468 100644 --- a/TUnit.Mocks/Arguments/RefStructArg.cs +++ b/TUnit.Mocks/Arguments/RefStructArg.cs @@ -30,7 +30,7 @@ public RefStructArg(IArgumentMatcher matcher) } /// Matches any value of the ref struct type. This is currently the only supported matcher for ref struct parameters. - public static RefStructArg Any => new(Mocks.Matchers.AnyMatcher.Instance); + public static RefStructArg Any => new(Matchers.AnyMatcher.Instance); } #endif diff --git a/TUnit.Mocks/MockEngine.cs b/TUnit.Mocks/MockEngine.cs index bdfecd36f7..bdeefe6f50 100644 --- a/TUnit.Mocks/MockEngine.cs +++ b/TUnit.Mocks/MockEngine.cs @@ -22,7 +22,7 @@ internal static class MockCallSequence public sealed class MockEngine : IMockEngineAccess where T : class { private readonly Dictionary> _setupsByMember = new(); - private readonly System.Threading.Lock _setupLock = new(); + private readonly Lock _setupLock = new(); private readonly ConcurrentQueue _callHistory = new(); private readonly ConcurrentDictionary _autoTrackValues = new(); private readonly ConcurrentQueue<(string EventName, bool IsSubscribe)> _eventSubscriptions = new(); @@ -134,7 +134,7 @@ public void HandleCall(int memberId, string memberName, object?[] args) { behavior.Execute(args); // Set out/ref assignments after Execute to avoid reentrancy overwrite from callbacks - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); if (matchedSetup is not null) { @@ -144,7 +144,7 @@ public void HandleCall(int memberId, string memberName, object?[] args) } // Set out/ref assignments for generated code to consume - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); // A matching setup with no explicit behavior means "allow this call" (e.g., void setup with no callback) if (setupFound) @@ -181,7 +181,7 @@ public TReturn HandleCallWithReturn(int memberId, string memberName, ob { var result = behavior.Execute(args); // Set out/ref assignments after Execute to avoid reentrancy overwrite from callbacks - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); if (matchedSetup is not null) { @@ -194,7 +194,7 @@ public TReturn HandleCallWithReturn(int memberId, string memberName, ob } // Set out/ref assignments for generated code to consume - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); // A matching setup with no explicit behavior returns the default value if (setupFound) @@ -273,7 +273,7 @@ public bool TryHandleCall(int memberId, string memberName, object?[] args) { behavior.Execute(args); // Set out/ref assignments after Execute to avoid reentrancy overwrite from callbacks - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); if (matchedSetup is not null) { @@ -283,7 +283,7 @@ public bool TryHandleCall(int memberId, string memberName, object?[] args) } // Set out/ref assignments for generated code to consume - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); if (setupFound && matchedSetup is not null) { @@ -322,7 +322,7 @@ public bool TryHandleCallWithReturn(int memberId, string memberName, ob { var behaviorResult = behavior.Execute(args); // Set out/ref assignments after Execute to avoid reentrancy overwrite from callbacks - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); if (matchedSetup is not null) { @@ -336,7 +336,7 @@ public bool TryHandleCallWithReturn(int memberId, string memberName, ob } // Set out/ref assignments for generated code to consume - Setup.OutRefContext.Set(matchedSetup?.OutRefAssignments); + OutRefContext.Set(matchedSetup?.OutRefAssignments); if (setupFound) { diff --git a/TUnit.Mocks/MockRepository.cs b/TUnit.Mocks/MockRepository.cs index 778cb15653..70b14d47b3 100644 --- a/TUnit.Mocks/MockRepository.cs +++ b/TUnit.Mocks/MockRepository.cs @@ -8,7 +8,7 @@ public class MockRepository { private readonly MockBehavior _defaultBehavior; private readonly List _mocks = new(); - private readonly System.Threading.Lock _lock = new(); + private readonly Lock _lock = new(); /// Creates a repository with as the default behavior. public MockRepository() : this(MockBehavior.Loose) { } diff --git a/TUnit.Mocks/Setup/MethodSetup.cs b/TUnit.Mocks/Setup/MethodSetup.cs index bd195f3fbe..ac52557340 100644 --- a/TUnit.Mocks/Setup/MethodSetup.cs +++ b/TUnit.Mocks/Setup/MethodSetup.cs @@ -11,7 +11,7 @@ namespace TUnit.Mocks.Setup; public sealed class MethodSetup { private readonly IArgumentMatcher[] _matchers; - private readonly System.Threading.Lock _behaviorLock = new(); + private readonly Lock _behaviorLock = new(); private readonly List _behaviors = new(); private readonly List _eventRaises = new(); private Dictionary? _outRefAssignments; diff --git a/TUnit.Mocks/TUnit.Mocks.targets b/TUnit.Mocks/TUnit.Mocks.targets index 78add3964b..96c60220a6 100644 --- a/TUnit.Mocks/TUnit.Mocks.targets +++ b/TUnit.Mocks/TUnit.Mocks.targets @@ -2,6 +2,7 @@ + diff --git a/docs/docs/assertions/null-and-default.md b/docs/docs/assertions/null-and-default.md index f47abc2f39..e681059388 100644 --- a/docs/docs/assertions/null-and-default.md +++ b/docs/docs/assertions/null-and-default.md @@ -233,7 +233,7 @@ public async Task Uninitialized_Field() [Test] public async Task Constructor_Injection() { - var logger = new Mock(); + var logger = Mock.Of(); var service = new UserService(logger.Object); // Verify dependency was injected diff --git a/docs/docs/assertions/types.md b/docs/docs/assertions/types.md index e831c7ebf2..9d1ab00cd8 100644 --- a/docs/docs/assertions/types.md +++ b/docs/docs/assertions/types.md @@ -631,7 +631,7 @@ public async Task Type_Is_Serializable() [Test] public async Task Mock_Implements_Interface() { - var mock = new Mock(); + var mock = Mock.Of(); var instance = mock.Object; await Assert.That(instance).IsAssignableTo(); diff --git a/docs/docs/guides/best-practices.md b/docs/docs/guides/best-practices.md index b67eec7ed8..56f998cef8 100644 --- a/docs/docs/guides/best-practices.md +++ b/docs/docs/guides/best-practices.md @@ -592,10 +592,10 @@ Don't mock everything. Use real implementations when they're fast and reliable: [Test] public async Task ProcessOrder() { - var mockLogger = new Mock(); - var mockValidator = new Mock(); - var mockCalculator = new Mock(); - var mockRepository = new Mock(); + var mockLogger = Mock.Of(); + var mockValidator = Mock.Of(); + var mockCalculator = Mock.Of(); + var mockRepository = Mock.Of(); // So much setup... } @@ -607,7 +607,7 @@ public async Task ProcessOrder() var logger = new NullLogger(); // Real lightweight implementation var validator = new OrderValidator(); // Real validator is fast var calculator = new PriceCalculator(); // Simple calculations - var mockRepository = new Mock(); // Mock database + var mockRepository = Mock.Of(); // Mock database // Much simpler! } @@ -624,13 +624,13 @@ Test behavior, not implementation. Your tests should verify what the code does, [Test] public async Task ProcessOrder_CallsRepositorySaveMethod() { - var mockRepository = new Mock(); + var mockRepository = Mock.Of(); var service = new OrderService(mockRepository.Object); await service.ProcessOrder(order); // Verifying method calls instead of behavior - mockRepository.Verify(r => r.Save(It.IsAny()), Times.Once); + mockRepository.Save(Any()).WasCalled(Times.Once); } // ✅ Good: Testing actual behavior diff --git a/docs/docs/test-authoring/mocking/advanced.md b/docs/docs/test-authoring/mocking/advanced.md index 61b07785e7..755a764dc6 100644 --- a/docs/docs/test-authoring/mocking/advanced.md +++ b/docs/docs/test-authoring/mocking/advanced.md @@ -33,7 +33,7 @@ mock.RaiseOnMessage("Hello!"); Trigger an event automatically when a method is called using the typed `.Raises{EventName}()` method on a setup chain: ```csharp -mock.SendMessage(Arg.Any()) +mock.SendMessage(Any()) .RaisesOnMessage("echo"); mock.Object.SendMessage("test"); @@ -148,8 +148,8 @@ var serviceMock = repo.Of(); var loggerMock = repo.Of(); // Configure each mock individually -serviceMock.GetData(Arg.Any()).Returns("result"); -loggerMock.Log(Arg.Any()); +serviceMock.GetData(Any()).Returns("result"); +loggerMock.Log(Any()); // Exercise code serviceMock.Object.GetData(1); @@ -181,8 +181,8 @@ repo.Reset(); // clear all mocks Get a diagnostic report of setup coverage and call matching: ```csharp -mock.GetUser(Arg.Any()).Returns(new User("Alice")); -mock.Delete(Arg.Any()); +mock.GetUser(Any()).Returns(new User("Alice")); +mock.Delete(Any()); svc.GetUser(1); // Delete was never called @@ -190,7 +190,7 @@ svc.GetUser(1); var diag = Mock.GetDiagnostics(mock); diag.TotalSetups; // 2 diag.ExercisedSetups; // 1 -diag.UnusedSetups; // [Delete(Arg.Any())] +diag.UnusedSetups; // [Delete(Any())] diag.UnmatchedCalls; // [] (all calls matched a setup) ``` @@ -228,7 +228,7 @@ The provider is consulted **before** auto-mocking and built-in smart defaults. Clear all setups, call history, state, and auto-tracked property values: ```csharp -mock.GetUser(Arg.Any()).Returns(new User("Alice")); +mock.GetUser(Any()).Returns(new User("Alice")); svc.GetUser(1); Mock.Reset(mock); diff --git a/docs/docs/test-authoring/mocking/setup.md b/docs/docs/test-authoring/mocking/setup.md index 034bb67e11..c5ef5d71f1 100644 --- a/docs/docs/test-authoring/mocking/setup.md +++ b/docs/docs/test-authoring/mocking/setup.md @@ -12,13 +12,13 @@ Methods are called directly on `Mock` — the chain method (`.Returns()`, `.T ```csharp // Fixed return value -mock.GetUser(Arg.Any()).Returns(new User("Alice")); +mock.GetUser(Any()).Returns(new User("Alice")); // Computed return value -mock.GetUser(Arg.Any()).Returns(() => new User(DateTime.Now.ToString())); +mock.GetUser(Any()).Returns(() => new User(DateTime.Now.ToString())); // Async methods — no special API needed -mock.GetUserAsync(Arg.Any()).Returns(new User("Alice")); +mock.GetUserAsync(Any()).Returns(new User("Alice")); // TUnit.Mocks auto-wraps the value in Task or ValueTask ``` @@ -26,10 +26,10 @@ mock.GetUserAsync(Arg.Any()).Returns(new User("Alice")); ```csharp // Throw a specific exception type -mock.Delete(Arg.Any()).Throws(); +mock.Delete(Any()).Throws(); // Throw a specific instance -mock.Delete(Arg.Any()).Throws(new ArgumentException("bad id")); +mock.Delete(Any()).Throws(new ArgumentException("bad id")); ``` ### Callbacks @@ -37,11 +37,11 @@ mock.Delete(Arg.Any()).Throws(new ArgumentException("bad id")); ```csharp // Simple callback var callCount = 0; -mock.Process(Arg.Any()) +mock.Process(Any()) .Callback(() => callCount++); // Callback with access to arguments -mock.Process(Arg.Any()) +mock.Process(Any()) .Callback((object?[] args) => Console.WriteLine($"Called with: {args[0]}")); ``` @@ -50,7 +50,7 @@ mock.Process(Arg.Any()) Use `.Then()` to define different behaviors for successive calls: ```csharp -mock.GetValue(Arg.Any()) +mock.GetValue(Any()) .Throws() // 1st call: throws .Then() .Returns("retry-succeeded") // 2nd call: returns value @@ -58,7 +58,7 @@ mock.GetValue(Arg.Any()) .Returns("cached"); // 3rd+ calls: returns this value // Shorthand for sequential return values -mock.GetValue(Arg.Any()) +mock.GetValue(Any()) .ReturnsSequentially("first", "second", "third"); // 1st: "first", 2nd: "second", 3rd+: "third" (last value repeats) ``` @@ -68,14 +68,14 @@ mock.GetValue(Arg.Any()) Void methods support `Callback` and `Throws` (but not `Returns`): ```csharp -mock.Log(Arg.Any()) +mock.Log(Any()) .Callback(() => { /* side effect */ }); -mock.Log(Arg.Any()) +mock.Log(Any()) .Throws(); ``` -Void methods are also eagerly registered — calling `mock.Log(Arg.Any())` without chaining is sufficient to "allow" the call in strict mode. +Void methods are also eagerly registered — calling `mock.Log(Any())` without chaining is sufficient to "allow" the call in strict mode. ## Property Setup @@ -104,7 +104,7 @@ mock.Name.ReturnsSequentially("first", "second"); mock.Count.Setter.Callback(() => Console.WriteLine("Count was set")); // React to a specific value being set -mock.Count.Set(Arg.Is(42)).Callback(() => Console.WriteLine("Count set to 42")); +mock.Count.Set(Is(42)).Callback(() => Console.WriteLine("Count set to 42")); // Throw on setter mock.Name.Setter.Throws(); @@ -144,7 +144,7 @@ bool found = svc.TryGet("key", out var value); **Ref parameters** are included in setup signatures and participate in argument matching. Use `.SetsRef{Name}()` to assign output values: ```csharp -mock.Swap(Arg.Any()) +mock.Swap(Any()) .SetsRefValue(99); int val = 42; @@ -168,7 +168,7 @@ public abstract class Calculator } var mock = Mock.OfPartial(); -mock.Multiply(Arg.Any(), Arg.Any()).Returns(99); +mock.Multiply(Any(), Any()).Returns(99); mock.Object.Add(2, 3); // 5 (base implementation) mock.Object.Multiply(2, 3); // 99 (mocked) @@ -186,7 +186,7 @@ Mock any delegate type: ```csharp var mock = Mock.OfDelegate>(); -mock.Invoke(Arg.Any()).Returns(42); +mock.Invoke(Any()).Returns(42); Func func = mock; var result = func("hello"); // 42 @@ -216,7 +216,7 @@ Create a single mock that implements multiple interfaces: ```csharp var mock = Mock.Of(); -mock.Log(Arg.Any()); // ILogger method +mock.Log(Any()); // ILogger method mock.Object.Log("test"); ((IDisposable)mock.Object).Dispose(); // IDisposable method @@ -229,7 +229,7 @@ Supports up to 4 interfaces: `Mock.Of()`. Setup methods return chain objects that support additional behaviors: ```csharp -mock.Process(Arg.Any()) +mock.Process(Any()) .Returns(true) .RaisesProcessCompleted(EventArgs.Empty) // strongly-typed auto-raise event .TransitionsTo("processed"); // state machine transition diff --git a/docs/docs/test-authoring/mocking/verification.md b/docs/docs/test-authoring/mocking/verification.md index de3ff19de9..f871c884ca 100644 --- a/docs/docs/test-authoring/mocking/verification.md +++ b/docs/docs/test-authoring/mocking/verification.md @@ -16,7 +16,7 @@ mock.GetUser(42).WasCalled(); mock.GetUser(42).WasCalled(Times.Once); // Verify never called -mock.Delete(Arg.Any()).WasNeverCalled(); +mock.Delete(Any()).WasNeverCalled(); ``` ### Times @@ -35,7 +35,7 @@ mock.Delete(Arg.Any()).WasNeverCalled(); ```csharp mock.GetUser(42).WasCalled(Times.Once, "GetUser should be called once during initialization"); -mock.Delete(Arg.Any()).WasNeverCalled("Delete should not be called in read-only mode"); +mock.Delete(Any()).WasNeverCalled("Delete should not be called in read-only mode"); ``` ## Property Verification @@ -54,7 +54,7 @@ mock.Count.Setter.WasNeverCalled(); // Setter verification — specific value mock.Count.Set(42).WasCalled(Times.Once); -mock.Count.Set(Arg.Is(v => v > 0)).WasCalled(Times.AtLeast(1)); +mock.Count.Set(Is(v => v > 0)).WasCalled(Times.AtLeast(1)); ``` ## Argument Matching in Verification @@ -66,10 +66,10 @@ Verification uses the same `Arg` matchers as setup: mock.GetUser(42).WasCalled(Times.Once); // Any value -mock.GetUser(Arg.Any()).WasCalled(Times.Exactly(3)); +mock.GetUser(Any()).WasCalled(Times.Exactly(3)); // Predicate -mock.GetUser(Arg.Is(id => id > 0)).WasCalled(Times.AtLeast(1)); +mock.GetUser(Is(id => id > 0)).WasCalled(Times.AtLeast(1)); ``` See [Argument Matchers](argument-matchers) for the full list of matchers. @@ -82,7 +82,7 @@ Verify calls occurred in a specific order **across one or more mocks**: Mock.VerifyInOrder(() => { mockLogger.Log("Starting").WasCalled(); - mockRepo.SaveAsync(Arg.Any()).WasCalled(); + mockRepo.SaveAsync(Any()).WasCalled(); mockLogger.Log("Done").WasCalled(); }); ``` @@ -98,8 +98,8 @@ If calls occurred out of order, `VerifyInOrder` throws with a message showing th Verify that **every setup** was invoked at least once: ```csharp -mock.GetUser(Arg.Any()).Returns(new User("Alice")); -mock.Delete(Arg.Any()); +mock.GetUser(Any()).Returns(new User("Alice")); +mock.Delete(Any()); svc.GetUser(1); svc.Delete(2); @@ -133,7 +133,7 @@ Use TUnit's `Assert.That` pipeline for assertion-style verification with better using TUnit.Mocks.Assertions; await Assert.That(mock.GetUser(42)).WasCalled(Times.Once); -await Assert.That(mock.Delete(Arg.Any())).WasNeverCalled(); +await Assert.That(mock.Delete(Any())).WasNeverCalled(); // Property verification through assertions await Assert.That(mock.Name).WasCalled(Times.Once); diff --git a/docs/docs/troubleshooting.md b/docs/docs/troubleshooting.md index 14839f3036..009164b524 100644 --- a/docs/docs/troubleshooting.md +++ b/docs/docs/troubleshooting.md @@ -858,7 +858,7 @@ public class SharedDatabaseTests ### Mocking HTTP Calls and External APIs -**Strategy 1: Using Moq with HttpClient** +**Strategy 1: Using TUnit.Mocks.Http** ```csharp public class WeatherServiceTests @@ -866,21 +866,12 @@ public class WeatherServiceTests [Test] public async Task GetWeather_ReturnsTemperature() { - // Arrange - var mockHandler = new Mock(); - mockHandler.Protected() - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("{\"temperature\": 22.5}") - }); + // Arrange — MockHttpClient is a real HttpClient with mock setup + using var client = Mock.HttpClient("https://api.weather.com"); + client.Handler.OnGet("/weather/London") + .RespondWithJson("""{"temperature": 22.5}"""); - var httpClient = new HttpClient(mockHandler.Object); - var service = new WeatherService(httpClient); + var service = new WeatherService(client); // Act var weather = await service.GetWeatherAsync("London"); @@ -1778,8 +1769,8 @@ public async Task IsBusinessHours() [Test] public async Task IsBusinessHours() { - var mockTime = new Mock(); - mockTime.Setup(t => t.Now).Returns(new DateTime(2024, 1, 15, 10, 0, 0)); // Monday 10 AM + var mockTime = Mock.Of(); + mockTime.Now.Returns(new DateTime(2024, 1, 15, 10, 0, 0)); // Monday 10 AM var service = new BusinessHoursService(mockTime.Object); var result = service.IsBusinessHours(); @@ -1807,18 +1798,11 @@ public async Task FetchUserData() [Test] public async Task FetchUserData() { - var mockHandler = new Mock(); - mockHandler.Protected() - .Setup>("SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - Content = new StringContent("{\"username\": \"alice\"}") - }); + using var client = Mock.HttpClient("https://api.example.com"); + client.Handler.OnGet("/users/1") + .RespondWithJson("""{"username": "alice"}"""); - var client = new HttpClient(mockHandler.Object); - var response = await client.GetStringAsync("https://api.example.com/users/1"); + var response = await client.GetStringAsync("/users/1"); await Assert.That(response).Contains("username"); // Always passes } From 3f1bb1752d9383e0785a232908e6966b50611de1 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:26:17 +0000 Subject: [PATCH 3/4] test(mocks): add Func-typed parameter ambiguity test Verify that when a mocked interface has a Func parameter, the generated Func, bool> overload doesn't conflict with the base Arg> overload. --- TUnit.Mocks.Tests/FuncOverloadTests.cs | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/TUnit.Mocks.Tests/FuncOverloadTests.cs b/TUnit.Mocks.Tests/FuncOverloadTests.cs index 9cbdf3d664..ff6eb8c867 100644 --- a/TUnit.Mocks.Tests/FuncOverloadTests.cs +++ b/TUnit.Mocks.Tests/FuncOverloadTests.cs @@ -3,6 +3,15 @@ namespace TUnit.Mocks.Tests; +/// +/// Interface whose parameter is itself a Func — tests that generated Func overloads +/// don't cause ambiguity with the base Arg<Func<int, bool>> overload. +/// +public interface IFilterService +{ + int Apply(Func filter); +} + /// /// Tests for Func<T, bool> parameter overloads that enable inline lambda syntax. /// Verifies that mock.Method(x => predicate, Any()) compiles and works correctly. @@ -184,6 +193,24 @@ public async Task String_Predicate_Lambda() await Assert.That(unmatched).IsEqualTo(""); } + [Test] + public async Task Func_Typed_Parameter_No_Ambiguity() + { + // Arrange — IFilterService.Apply takes Func as a parameter. + // The base overload is Arg> and the generated Func overload + // is Func, bool>. Passing a Func value should + // target the base overload (implicit T -> Arg), not cause ambiguity. + var mock = Mock.Of(); + Func isPositive = x => x > 0; + mock.Apply(isPositive).Returns(42); + + // Act + var result = mock.Object.Apply(isPositive); + + // Assert + await Assert.That(result).IsEqualTo(42); + } + [Test] public async Task Multiple_Lambda_Setups_Last_Wins() { From 637c3e72dc8281cccb412cfad2902436a608afbd Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:36:59 +0000 Subject: [PATCH 4/4] =?UTF-8?q?refactor(mocks):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20AnyArg=20sentinel,=20reduce=20overloads,=20extract?= =?UTF-8?q?=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove global AD0001 suppression from Library.props (not needed) - Refactor Arg back to static class; extract AnyArg sentinel type for untyped Any() to keep single-responsibility (factory vs sentinel) - Cap Func overload generation at 4 params (MaxFuncOverloadParams=4, max 15 overloads) separate from MaxTypedParams=8 for wrappers - Extract GetReturnTypeInfo() helper shared by EmitMemberMethodBody and EmitSingleFuncOverload to eliminate return-type logic duplication - Improve ordering test to verify both first and last setup are active --- Library.props | 1 - .../Builders/MockMembersBuilder.cs | 37 +++++++------------ TUnit.Mocks.Tests/FuncOverloadTests.cs | 8 ++-- TUnit.Mocks/Arguments/Arg.cs | 20 +++++++--- TUnit.Mocks/Arguments/ArgOfT.cs | 2 +- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/Library.props b/Library.props index fb48df06f6..a4f9a83316 100644 --- a/Library.props +++ b/Library.props @@ -3,7 +3,6 @@ netstandard2.0;net8.0;net9.0;net10.0 true - $(NoWarn);AD0001 false diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs index 9d948177fc..7e352d1bf3 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs @@ -13,6 +13,7 @@ namespace TUnit.Mocks.SourceGenerator.Builders; internal static class MockMembersBuilder { private const int MaxTypedParams = 8; + private const int MaxFuncOverloadParams = 4; private static readonly HashSet MockMemberNames = new(System.StringComparer.Ordinal) { @@ -529,31 +530,30 @@ private static void GenerateMemberMethod(CodeWriter writer, MockMemberModel meth } } - private static void EmitMemberMethodBody(CodeWriter writer, MockMemberModel method, MockTypeModel model, string safeName, bool includeRefStructArgs) + private static (bool UseTypedWrapper, string ReturnType, string SetupReturnType) GetReturnTypeInfo( + MockMemberModel method, MockTypeModel model, string safeName) { - // For async methods (Task/ValueTask), unwrap the return type so users write .Returns(5) not .Returns(Task.FromResult(5)) - // For void-async methods (Task/ValueTask), IsVoid is already true var setupReturnType = method.IsAsync && !method.IsVoid ? method.UnwrappedReturnType : method.ReturnType; var hasEvents = model.Events.Length > 0; var useTypedWrapper = ShouldGenerateTypedWrapper(method, hasEvents); - string returnType; + string returnType; if (useTypedWrapper) - { returnType = GetWrapperName(safeName, method); - } else if (method.IsVoid || method.IsRefStructReturn) - { - // Ref struct returns use VoidMockMethodCall (can't use ref struct as generic type arg) returnType = "global::TUnit.Mocks.VoidMockMethodCall"; - } else - { returnType = $"global::TUnit.Mocks.MockMethodCall<{setupReturnType}>"; - } + + return (useTypedWrapper, returnType, setupReturnType); + } + + private static void EmitMemberMethodBody(CodeWriter writer, MockMemberModel method, MockTypeModel model, string safeName, bool includeRefStructArgs) + { + var (useTypedWrapper, returnType, setupReturnType) = GetReturnTypeInfo(method, model, safeName); var paramList = GetArgParameterList(method, includeRefStructArgs); var typeParams = GetTypeParameterList(method); @@ -615,7 +615,7 @@ private static void EmitFuncOverloads(CodeWriter writer, MockMemberModel method, string safeName, bool includeRefStructArgs) { var eligible = GetFuncEligibleParamIndices(method); - if (eligible.Count == 0 || eligible.Count > MaxTypedParams) return; + if (eligible.Count == 0 || eligible.Count > MaxFuncOverloadParams) return; int totalMasks = (1 << eligible.Count) - 1; for (int mask = 1; mask <= totalMasks; mask++) @@ -636,18 +636,7 @@ private static void EmitSingleFuncOverload(CodeWriter writer, MockMemberModel me funcIndices.Add(eligibleIndices[bit]); } - // Return type (same logic as EmitMemberMethodBody) - var setupReturnType = method.IsAsync && !method.IsVoid ? method.UnwrappedReturnType : method.ReturnType; - var hasEvents = model.Events.Length > 0; - var useTypedWrapper = ShouldGenerateTypedWrapper(method, hasEvents); - - string returnType; - if (useTypedWrapper) - returnType = GetWrapperName(safeName, method); - else if (method.IsVoid || method.IsRefStructReturn) - returnType = "global::TUnit.Mocks.VoidMockMethodCall"; - else - returnType = $"global::TUnit.Mocks.MockMethodCall<{setupReturnType}>"; + var (useTypedWrapper, returnType, setupReturnType) = GetReturnTypeInfo(method, model, safeName); // Build mixed parameter list var paramParts = new List(); diff --git a/TUnit.Mocks.Tests/FuncOverloadTests.cs b/TUnit.Mocks.Tests/FuncOverloadTests.cs index ff6eb8c867..42355433dd 100644 --- a/TUnit.Mocks.Tests/FuncOverloadTests.cs +++ b/TUnit.Mocks.Tests/FuncOverloadTests.cs @@ -219,10 +219,10 @@ public async Task Multiple_Lambda_Setups_Last_Wins() mock.Add(x => x > 0, Any()).Returns(50); mock.Add(x => x > 10, Any()).Returns(100); - // Act — 15 matches both, but last-registered wins - var result = mock.Object.Add(15, 0); + // Act & Assert — value matching only the first setup returns 50 + await Assert.That(mock.Object.Add(5, 0)).IsEqualTo(50); - // Assert - await Assert.That(result).IsEqualTo(100); + // Act & Assert — value matching both setups returns 100 (last-registered wins) + await Assert.That(mock.Object.Add(15, 0)).IsEqualTo(100); } } diff --git a/TUnit.Mocks/Arguments/Arg.cs b/TUnit.Mocks/Arguments/Arg.cs index a3c60c70bf..ce3b4f750e 100644 --- a/TUnit.Mocks/Arguments/Arg.cs +++ b/TUnit.Mocks/Arguments/Arg.cs @@ -7,15 +7,14 @@ namespace TUnit.Mocks.Arguments; /// Provides static factory methods for creating argument matchers /// used in mock setup and verification expressions. /// -public class Arg +public static class Arg { - private Arg() - { - } - /// Matches any value of the specified type, including null. public static Arg Any() => new(new AnyMatcher()); + /// Matches any value — type is inferred from the parameter position. + public static AnyArg Any() => AnyArg.Instance; + /// Matches using exact equality. public static Arg Is(T value) => new(new ExactMatcher(value)); @@ -65,6 +64,15 @@ public static Arg IsInRange(T min, T max) where T : IComparable /// Negates the inner matcher -- matches when the inner matcher does NOT match. public static Arg Not(Arg inner) => new(new NotMatcher(inner.Matcher)); +} - public static Arg Any() => new(); +/// +/// Sentinel type returned by that implicitly converts to +/// for any T, enabling untyped Any() calls where the type is inferred from context. +/// +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +public sealed class AnyArg +{ + internal static readonly AnyArg Instance = new(); + private AnyArg() { } } diff --git a/TUnit.Mocks/Arguments/ArgOfT.cs b/TUnit.Mocks/Arguments/ArgOfT.cs index 979974a2d0..52a879a05d 100644 --- a/TUnit.Mocks/Arguments/ArgOfT.cs +++ b/TUnit.Mocks/Arguments/ArgOfT.cs @@ -38,5 +38,5 @@ public Arg(IArgumentMatcher matcher) /// The predicate that determines whether an argument matches. public static implicit operator Arg(Func predicate) => new(new PredicateMatcher(predicate!)); - public static implicit operator Arg(Arg arg) => new(new AnyMatcher()); + public static implicit operator Arg(AnyArg _) => new(new AnyMatcher()); }