Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ namespace TUnit.Mocks.Generated
return new IEscapedNames_namespace_M3_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 3, "namespace", matchers);
}

/// <summary>Configure the mock setup for <c>namespace</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>
public static IEscapedNames_namespace_M3_MockCall @namespace(this global::TUnit.Mocks.Mock<global::IEscapedNames> mock, global::TUnit.Mocks.Arguments.AnyArgs _)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance };
return new IEscapedNames_namespace_M3_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 3, "namespace", matchers);
}

extension(global::TUnit.Mocks.Mock<global::IEscapedNames> mock)
{
public global::TUnit.Mocks.PropertyMockCall<int> @class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ namespace TUnit.Mocks.Generated
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_class.Matcher, __fa_return.Matcher };
return new ITest_Get_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "Get", matchers);
}

/// <summary>Configure the mock setup for <c>Get</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>
public static ITest_Get_M1_MockCall Get(this global::TUnit.Mocks.Mock<global::ITest> mock, global::TUnit.Mocks.Arguments.AnyArgs _)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<string>.Instance };
return new ITest_Get_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "Get", matchers);
}
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ namespace TUnit.Mocks.Generated
return new IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "GetValue", matchers);
}

/// <summary>Configure the mock setup for <c>GetValue</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>
public static IFoo_GetValue_M1_MockCall GetValue(this global::TUnit.Mocks.Mock<global::IFoo> mock, global::TUnit.Mocks.Arguments.AnyArgs _)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { global::TUnit.Mocks.Matchers.AnyMatcher<string?>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance };
return new IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "GetValue", matchers);
}

public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock<global::IFoo> mock, global::TUnit.Mocks.Arguments.Arg<string> nonNull, global::TUnit.Mocks.Arguments.Arg<string?> nullable, global::TUnit.Mocks.Arguments.Arg<object?> obj)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { nonNull.Matcher, nullable.Matcher, obj.Matcher };
Expand Down Expand Up @@ -233,6 +240,13 @@ namespace TUnit.Mocks.Generated
return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 2, "Process", matchers);
}

/// <summary>Configure the mock setup for <c>Process</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>
public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock<global::IFoo> mock, global::TUnit.Mocks.Arguments.AnyArgs _)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { global::TUnit.Mocks.Matchers.AnyMatcher<string>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<string?>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<object?>.Instance };
return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 2, "Process", matchers);
}

public static IFoo_GetAsync_M3_MockCall GetAsync(this global::TUnit.Mocks.Mock<global::IFoo> mock, global::TUnit.Mocks.Arguments.Arg<string?> key)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { key.Matcher };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,13 @@ namespace TUnit.Mocks.Generated
return new IDialogService_Show_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "Show", matchers);
}

/// <summary>Configure the mock setup for <c>Show</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>
public static IDialogService_Show_M0_MockCall Show(this global::TUnit.Mocks.Mock<global::IDialogService> mock, global::TUnit.Mocks.Arguments.AnyArgs _)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { global::TUnit.Mocks.Matchers.AnyMatcher<string?>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<string?>.Instance };
return new IDialogService_Show_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "Show", matchers);
}

public static global::TUnit.Mocks.MockMethodCall<string?> ShowPanel<TData>(this global::TUnit.Mocks.Mock<global::IDialogService> mock, global::TUnit.Mocks.Arguments.Arg<TData?> data) where TData : class
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { data.Matcher };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ namespace TUnit.Mocks.Generated
return new ICalculator_Add_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "Add", matchers);
}

/// <summary>Configure the mock setup for <c>Add</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>
public static ICalculator_Add_M0_MockCall Add(this global::TUnit.Mocks.Mock<global::ICalculator> mock, global::TUnit.Mocks.Arguments.AnyArgs _)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance };
return new ICalculator_Add_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "Add", matchers);
}

public static ICalculator_Subtract_M1_MockCall Subtract(this global::TUnit.Mocks.Mock<global::ICalculator> mock, global::TUnit.Mocks.Arguments.Arg<int> a, global::TUnit.Mocks.Arguments.Arg<int> b)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { a.Matcher, b.Matcher };
Expand Down Expand Up @@ -157,6 +164,13 @@ namespace TUnit.Mocks.Generated
return new ICalculator_Subtract_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "Subtract", matchers);
}

/// <summary>Configure the mock setup for <c>Subtract</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>
public static ICalculator_Subtract_M1_MockCall Subtract(this global::TUnit.Mocks.Mock<global::ICalculator> mock, global::TUnit.Mocks.Arguments.AnyArgs _)
{
var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance, global::TUnit.Mocks.Matchers.AnyMatcher<int>.Instance };
return new ICalculator_Subtract_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "Subtract", matchers);
}

public static global::TUnit.Mocks.VoidMockMethodCall Reset(this global::TUnit.Mocks.Mock<global::ICalculator> mock)
{
var matchers = global::System.Array.Empty<global::TUnit.Mocks.Arguments.IArgumentMatcher>();
Expand Down
135 changes: 102 additions & 33 deletions TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,8 @@ private static void GenerateMemberMethod(CodeWriter writer, MockMemberModel meth
EmitMemberMethodBody(writer, method, model, safeName, includeRefStructArgs: false, captureModelTypeParameters: false, receiverIsThis: false);
EmitFuncOverloads(writer, method, model, safeName, includeRefStructArgs: false, captureModelTypeParameters: false, receiverIsThis: false);
}

EmitAnyArgsOverload(writer, method, model, safeName, captureModelTypeParameters: false, receiverIsThis: false);
}

private static void GenerateGenericMethodExtensionBlock(CodeWriter writer, MockMemberModel method, MockTypeModel model, string safeName)
Expand Down Expand Up @@ -716,6 +718,8 @@ private static void GenerateGenericMethodMembersInCurrentBlock(CodeWriter writer
EmitMemberMethodBody(writer, method, model, safeName, includeRefStructArgs: false, captureModelTypeParameters: true, receiverIsThis: false);
EmitFuncOverloads(writer, method, model, safeName, includeRefStructArgs: false, captureModelTypeParameters: true, receiverIsThis: false);
}

EmitAnyArgsOverload(writer, method, model, safeName, captureModelTypeParameters: true, receiverIsThis: false);
}

internal static void GenerateGenericMethodMembersForWrapper(CodeWriter writer, MockMemberModel method, MockTypeModel model, string safeName)
Expand All @@ -735,6 +739,8 @@ internal static void GenerateGenericMethodMembersForWrapper(CodeWriter writer, M
EmitMemberMethodBody(writer, method, model, safeName, includeRefStructArgs: false, captureModelTypeParameters: true, receiverIsThis: true);
EmitFuncOverloads(writer, method, model, safeName, includeRefStructArgs: false, captureModelTypeParameters: true, receiverIsThis: true);
}

EmitAnyArgsOverload(writer, method, model, safeName, captureModelTypeParameters: true, receiverIsThis: true);
}

/// <summary>
Expand Down Expand Up @@ -869,23 +875,103 @@ private static void EmitMemberMethodBody(CodeWriter writer, MockMemberModel meth
writer.AppendLine($"var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] {{ {matcherArgs} }};");
}

if (useTypedWrapper)
{
var wrapperName = MockImplBuilder.GetGeneratedTypeName(GetWrapperName(safeName, method), model);
writer.AppendLine($"return new {wrapperName}(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else if (method.IsVoid || method.IsRefStructReturn)
{
writer.AppendLine($"return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"return new global::TUnit.Mocks.MockMethodCall<object?>(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else
EmitReturnConstruction(writer, method, model, safeName, useTypedWrapper, setupReturnType);
}
}

/// <summary>
/// Emits the dispatch tail shared by every setup overload: build a return statement constructing
/// the right wrapper / mock-call type given the matchers array already in scope as <c>matchers</c>.
/// </summary>
private static void EmitReturnConstruction(CodeWriter writer, MockMemberModel method, MockTypeModel model,
string safeName, bool useTypedWrapper, string setupReturnType)
{
if (useTypedWrapper)
{
var wrapperName = MockImplBuilder.GetGeneratedTypeName(GetWrapperName(safeName, method), model);
writer.AppendLine($"return new {wrapperName}(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else if (method.IsVoid || method.IsRefStructReturn)
{
writer.AppendLine($"return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"return new global::TUnit.Mocks.MockMethodCall<object?>(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else
{
writer.AppendLine($"return new global::TUnit.Mocks.MockMethodCall<{setupReturnType}>(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
}

/// <summary>
/// Emits a single-argument shortcut overload that accepts <c>AnyArgs</c> and fills every
/// matchable parameter slot with <c>AnyMatcher&lt;T&gt;.Instance</c>. Skipped when the shortcut
/// would be ambiguous (the method name is not unique on the model), unhelpful (zero or one
/// matchable parameter), unable to infer method type parameters, or unsafe to mirror at this
/// layer (out / ref / ref-struct params).
/// </summary>
private static void EmitAnyArgsOverload(CodeWriter writer, MockMemberModel method, MockTypeModel model,
string safeName, bool captureModelTypeParameters, bool receiverIsThis)
{
// AnyArgs carries no typed argument information, so generic method type parameters
// cannot be inferred at the call site. Defer to the explicit per-parameter overload.
if (method.IsGenericMethod) return;

// Out, ref, and ref-struct params change matcher arity or signature shape; rather than
// try to mirror those variations through the AnyArgs path, defer to the explicit form.
foreach (var p in method.Parameters)
{
if (p.Direction == ParameterDirection.Out || p.Direction == ParameterDirection.Ref) return;
if (p.IsRefStruct) return;
}

// After the early-return loop above, every parameter is matchable.
if (method.Parameters.Length < 2) return;

// Name uniqueness: same set of methods that drive extension-method emission.
int sameNameCount = 0;
foreach (var m in model.Methods)
{
if (m.ExplicitInterfaceName is not null && !m.IsStaticAbstract) continue;
if (m.Name == method.Name) sameNameCount++;
}
if (sameNameCount > 1) return;

var (useTypedWrapper, returnType, setupReturnType) = GetReturnTypeInfo(method, model, safeName);

var typeParams = captureModelTypeParameters
? MockImplBuilder.GetTypeParameterList(method)
: GetCombinedTypeParameterList(model, method);
var constraints = captureModelTypeParameters
? MockImplBuilder.GetConstraintClauses(method)
: GetCombinedConstraintClauses(model, method);

var safeMemberName = GetSafeMemberName(method.Name);
var paramListInner = "global::TUnit.Mocks.Arguments.AnyArgs _";
var fullParamList = captureModelTypeParameters
? paramListInner
: BuildExtensionMethodParameterList(model, paramListInner);

var methodDeclarationPrefix = captureModelTypeParameters ? "public" : "public static";

writer.AppendLine();
writer.AppendLine($"/// <summary>Configure the mock setup for <c>{method.Name}</c> with every argument matched as <c>Any&lt;T&gt;()</c>.</summary>");
using (writer.Block($"{methodDeclarationPrefix} {returnType} {safeMemberName}{typeParams}({fullParamList}){constraints}"))
{
if (receiverIsThis)
{
writer.AppendLine($"return new global::TUnit.Mocks.MockMethodCall<{setupReturnType}>(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
writer.AppendLine("var mock = this;");
}

// AnyMatcher<T>.Instance is stateless and shared — unlike Arg.Any<T>(), it skips the
// CapturingMatcher wrap, so the AnyArgs path doesn't accumulate captured values per call.
var matcherArgs = string.Join(", ", method.Parameters.Select(p =>
$"global::TUnit.Mocks.Matchers.AnyMatcher<{p.FullyQualifiedType}>.Instance"));
writer.AppendLine($"var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] {{ {matcherArgs} }};");

EmitReturnConstruction(writer, method, model, safeName, useTypedWrapper, setupReturnType);
}
}

Expand Down Expand Up @@ -1029,24 +1115,7 @@ private static void EmitSingleFuncOverload(CodeWriter writer, MockMemberModel me
writer.AppendLine($"var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] {{ {string.Join(", ", matcherExprs)} }};");
}

// Return statement
if (useTypedWrapper)
{
var wrapperName = MockImplBuilder.GetGeneratedTypeName(GetWrapperName(safeName, method), model);
writer.AppendLine($"return new {wrapperName}(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else if (method.IsVoid || method.IsRefStructReturn)
{
writer.AppendLine($"return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"return new global::TUnit.Mocks.MockMethodCall<object?>(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
else
{
writer.AppendLine($"return new global::TUnit.Mocks.MockMethodCall<{setupReturnType}>(global::TUnit.Mocks.MockRegistry.GetEngine(mock), {method.MemberId}, \"{method.Name}\", matchers);");
}
EmitReturnConstruction(writer, method, model, safeName, useTypedWrapper, setupReturnType);
}
}

Expand Down
Loading
Loading