From 48021ccaeda58075760280d6a16c307708fa1de4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 06:55:26 +0000 Subject: [PATCH 1/4] Initial plan From a4c3722c891121084b25f888c1dbd23ce6744581 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 07:04:43 +0000 Subject: [PATCH 2/4] Fix: preserve nullability of reference type parameters in mock generator When generating mock implementations, the source generator now correctly preserves the `?` annotation on nullable reference type parameters. Previously, `object? baz` would become `object baz` in the generated code, causing CS8767 warnings. Added GetFullyQualifiedNameWithNullability() and GetMinimallyQualifiedNameWithNullability() helper methods that append `?` for nullable reference types. Updated all MockParameterModel creation sites in MemberDiscovery.cs to use these helpers. Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/c26ff668-902b-4b2f-a63c-526123f1340c --- .../MockGeneratorTests.cs | 25 ++ ...ble_Reference_Type_Parameters.verified.txt | 415 ++++++++++++++++++ .../Discovery/MemberDiscovery.cs | 16 +- .../Extensions/MethodSymbolExtensions.cs | 2 +- .../Extensions/TypeSymbolExtensions.cs | 20 + 5 files changed, 469 insertions(+), 9 deletions(-) create mode 100644 TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt diff --git a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs index bd059cadec..5f4b9ccf3d 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs +++ b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs @@ -415,4 +415,29 @@ void M() return VerifyGeneratorOutput(source); } + + [Test] + public Task Interface_With_Nullable_Reference_Type_Parameters() + { + var source = """ + using TUnit.Mocks; + + public interface IFoo + { + void Bar(object? baz); + string? GetValue(string? key, int count); + void Process(string nonNull, string? nullable, object? obj); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + } + } + """; + + return VerifyGeneratorOutput(source); + } } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt new file mode 100644 index 0000000000..65a507aaa3 --- /dev/null +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt @@ -0,0 +1,415 @@ +// +#nullable enable + +namespace TUnit.Mocks.Generated +{ + internal static class IFoo_MockFactory + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.Mock.RegisterFactory(Create); + } + + private static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new IFoo_MockImpl(engine); + engine.Raisable = impl; + var mock = new global::TUnit.Mocks.Mock(impl, engine); + return mock; + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#nullable enable + +namespace TUnit.Mocks.Generated +{ + internal sealed class IFoo_MockImpl : global::IFoo, global::TUnit.Mocks.IRaisable + { + private readonly global::TUnit.Mocks.MockEngine _engine; + + internal IFoo_MockImpl(global::TUnit.Mocks.MockEngine engine) + { + _engine = engine; + } + + public void Bar(object? baz) + { + _engine.HandleCall(0, "Bar", new object?[] { baz }); + } + + public string GetValue(string? key, int count) + { + return _engine.HandleCallWithReturn(1, "GetValue", new object?[] { key, count }, default); + } + + public void Process(string nonNull, string? nullable, object? obj) + { + _engine.HandleCall(2, "Process", new object?[] { nonNull, nullable, obj }); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class IFoo_MockMemberExtensions + { + public static IFoo_Bar_M0_MockCall Bar(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg baz) + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { baz.Matcher }; + return new IFoo_Bar_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Bar", matchers); + } + + public static IFoo_Bar_M0_MockCall Bar(this global::TUnit.Mocks.Mock mock, global::System.Func baz) + { + global::TUnit.Mocks.Arguments.Arg __fa_baz = baz; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_baz.Matcher }; + return new IFoo_Bar_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "Bar", matchers); + } + + public static IFoo_GetValue_M1_MockCall GetValue(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg key, global::TUnit.Mocks.Arguments.Arg count) + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { key.Matcher, count.Matcher }; + return new IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "GetValue", matchers); + } + + public static IFoo_GetValue_M1_MockCall GetValue(this global::TUnit.Mocks.Mock mock, global::System.Func key, global::TUnit.Mocks.Arguments.Arg count) + { + global::TUnit.Mocks.Arguments.Arg __fa_key = key; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_key.Matcher, count.Matcher }; + return new IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "GetValue", matchers); + } + + public static IFoo_GetValue_M1_MockCall GetValue(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg key, global::System.Func count) + { + global::TUnit.Mocks.Arguments.Arg __fa_count = count; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { key.Matcher, __fa_count.Matcher }; + return new IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "GetValue", matchers); + } + + public static IFoo_GetValue_M1_MockCall GetValue(this global::TUnit.Mocks.Mock mock, global::System.Func key, global::System.Func count) + { + global::TUnit.Mocks.Arguments.Arg __fa_key = key; + global::TUnit.Mocks.Arguments.Arg __fa_count = count; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_key.Matcher, __fa_count.Matcher }; + return new IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "GetValue", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg nonNull, global::TUnit.Mocks.Arguments.Arg nullable, global::TUnit.Mocks.Arguments.Arg obj) + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { nonNull.Matcher, nullable.Matcher, obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::System.Func nonNull, global::TUnit.Mocks.Arguments.Arg nullable, global::TUnit.Mocks.Arguments.Arg obj) + { + global::TUnit.Mocks.Arguments.Arg __fa_nonNull = nonNull; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_nonNull.Matcher, nullable.Matcher, obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg nonNull, global::System.Func nullable, global::TUnit.Mocks.Arguments.Arg obj) + { + global::TUnit.Mocks.Arguments.Arg __fa_nullable = nullable; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { nonNull.Matcher, __fa_nullable.Matcher, obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::System.Func nonNull, global::System.Func nullable, global::TUnit.Mocks.Arguments.Arg obj) + { + global::TUnit.Mocks.Arguments.Arg __fa_nonNull = nonNull; + global::TUnit.Mocks.Arguments.Arg __fa_nullable = nullable; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_nonNull.Matcher, __fa_nullable.Matcher, obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg nonNull, global::TUnit.Mocks.Arguments.Arg nullable, global::System.Func obj) + { + global::TUnit.Mocks.Arguments.Arg __fa_obj = obj; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { nonNull.Matcher, nullable.Matcher, __fa_obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::System.Func nonNull, global::TUnit.Mocks.Arguments.Arg nullable, global::System.Func obj) + { + global::TUnit.Mocks.Arguments.Arg __fa_nonNull = nonNull; + global::TUnit.Mocks.Arguments.Arg __fa_obj = obj; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_nonNull.Matcher, nullable.Matcher, __fa_obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg nonNull, global::System.Func nullable, global::System.Func obj) + { + global::TUnit.Mocks.Arguments.Arg __fa_nullable = nullable; + global::TUnit.Mocks.Arguments.Arg __fa_obj = obj; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { nonNull.Matcher, __fa_nullable.Matcher, __fa_obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + + public static IFoo_Process_M2_MockCall Process(this global::TUnit.Mocks.Mock mock, global::System.Func nonNull, global::System.Func nullable, global::System.Func obj) + { + global::TUnit.Mocks.Arguments.Arg __fa_nonNull = nonNull; + global::TUnit.Mocks.Arguments.Arg __fa_nullable = nullable; + global::TUnit.Mocks.Arguments.Arg __fa_obj = obj; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_nonNull.Matcher, __fa_nullable.Matcher, __fa_obj.Matcher }; + return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IFoo_Bar_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private readonly global::System.Lazy _lazyBuilder; + + internal IFoo_Bar_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + _lazyBuilder = new global::System.Lazy(() => + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + _engine.AddSetup(setup); + return new global::TUnit.Mocks.Setup.VoidMethodSetupBuilder(setup); + } + ); + _ = _lazyBuilder.Value; + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; + + /// + public IFoo_Bar_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IFoo_Bar_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IFoo_Bar_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_Bar_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_Bar_M0_MockCall Throws(global::System.Func exceptionFactory) { EnsureSetup().Throws(exceptionFactory); return this; } + /// + public IFoo_Bar_M0_MockCall Raises(string eventName, object? args = null) { EnsureSetup().Raises(eventName, args); return this; } + /// + public IFoo_Bar_M0_MockCall SetsOutParameter(int paramIndex, object? value) { EnsureSetup().SetsOutParameter(paramIndex, value); return this; } + /// + public IFoo_Bar_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IFoo_Bar_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Execute a typed callback using the actual method parameters. + public IFoo_Bar_M0_MockCall Callback(global::System.Action callback) + { + EnsureSetup().Callback(args => callback((object?)args[0]!)); + return this; + } + + /// Configure a typed computed exception using the actual method parameters. + public IFoo_Bar_M0_MockCall Throws(global::System.Func exceptionFactory) + { + EnsureSetup().Throws(args => exceptionFactory((object?)args[0]!)); + return this; + } + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IFoo_GetValue_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private readonly global::System.Lazy> _lazyBuilder; + + internal IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + _lazyBuilder = new global::System.Lazy>(() => + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + _engine.AddSetup(setup); + return new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + } + ); + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; + + /// + public IFoo_GetValue_M1_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } + /// + public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IFoo_GetValue_M1_MockCall ReturnsSequentially(params string[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IFoo_GetValue_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IFoo_GetValue_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IFoo_GetValue_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_GetValue_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IFoo_GetValue_M1_MockCall Throws(global::System.Func exceptionFactory) { EnsureSetup().Throws(exceptionFactory); return this; } + /// + public IFoo_GetValue_M1_MockCall Raises(string eventName, object? args = null) { EnsureSetup().Raises(eventName, args); return this; } + /// + public IFoo_GetValue_M1_MockCall SetsOutParameter(int paramIndex, object? value) { EnsureSetup().SetsOutParameter(paramIndex, value); return this; } + /// + public IFoo_GetValue_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IFoo_GetValue_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Configure a typed computed return value using the actual method parameters. + public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) + { + EnsureSetup().Returns(args => factory((string?)args[0]!, (int)args[1]!)); + return this; + } + + /// Execute a typed callback using the actual method parameters. + public IFoo_GetValue_M1_MockCall Callback(global::System.Action callback) + { + EnsureSetup().Callback(args => callback((string?)args[0]!, (int)args[1]!)); + return this; + } + + /// Configure a typed computed exception using the actual method parameters. + public IFoo_GetValue_M1_MockCall Throws(global::System.Func exceptionFactory) + { + EnsureSetup().Throws(args => exceptionFactory((string?)args[0]!, (int)args[1]!)); + return this; + } + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IFoo_Process_M2_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private readonly global::System.Lazy _lazyBuilder; + + internal IFoo_Process_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + _lazyBuilder = new global::System.Lazy(() => + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + _engine.AddSetup(setup); + return new global::TUnit.Mocks.Setup.VoidMethodSetupBuilder(setup); + } + ); + _ = _lazyBuilder.Value; + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; + + /// + public IFoo_Process_M2_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IFoo_Process_M2_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IFoo_Process_M2_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_Process_M2_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_Process_M2_MockCall Throws(global::System.Func exceptionFactory) { EnsureSetup().Throws(exceptionFactory); return this; } + /// + public IFoo_Process_M2_MockCall Raises(string eventName, object? args = null) { EnsureSetup().Raises(eventName, args); return this; } + /// + public IFoo_Process_M2_MockCall SetsOutParameter(int paramIndex, object? value) { EnsureSetup().SetsOutParameter(paramIndex, value); return this; } + /// + public IFoo_Process_M2_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IFoo_Process_M2_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Execute a typed callback using the actual method parameters. + public IFoo_Process_M2_MockCall Callback(global::System.Action callback) + { + EnsureSetup().Callback(args => callback((string)args[0]!, (string?)args[1]!, (object?)args[2]!)); + return this; + } + + /// Configure a typed computed exception using the actual method parameters. + public IFoo_Process_M2_MockCall Throws(global::System.Func exceptionFactory) + { + EnsureSetup().Throws(args => exceptionFactory((string)args[0]!, (string?)args[1]!, (object?)args[2]!)); + return this; + } + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } +} diff --git a/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs b/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs index 7dc063b15d..3590e84a50 100644 --- a/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs +++ b/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs @@ -318,8 +318,8 @@ private static MockMemberModel CreateMethodModel(IMethodSymbol method, ref int m method.Parameters.Select(p => new MockParameterModel { Name = EscapeIdentifier(p.Name), - Type = p.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), - FullyQualifiedType = p.Type.GetFullyQualifiedName(), + Type = p.Type.GetMinimallyQualifiedNameWithNullability(), + FullyQualifiedType = p.Type.GetFullyQualifiedNameWithNullability(), Direction = p.GetParameterDirection(), HasDefaultValue = p.HasExplicitDefaultValue, DefaultValueExpression = p.HasExplicitDefaultValue ? FormatDefaultValue(p) : null, @@ -418,8 +418,8 @@ public static EquatableArray DiscoverConstructors(INamedTy ctor.Parameters.Select(p => new MockParameterModel { Name = EscapeIdentifier(p.Name), - Type = p.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), - FullyQualifiedType = p.Type.GetFullyQualifiedName(), + Type = p.Type.GetMinimallyQualifiedNameWithNullability(), + FullyQualifiedType = p.Type.GetFullyQualifiedNameWithNullability(), Direction = p.GetParameterDirection(), HasDefaultValue = p.HasExplicitDefaultValue, DefaultValueExpression = p.HasExplicitDefaultValue ? FormatDefaultValue(p) : null, @@ -455,8 +455,8 @@ private static MockMemberModel CreateIndexerModel(IPropertySymbol indexer, ref i indexer.Parameters.Select(p => new MockParameterModel { Name = EscapeIdentifier(p.Name), - Type = p.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), - FullyQualifiedType = p.Type.GetFullyQualifiedName(), + Type = p.Type.GetMinimallyQualifiedNameWithNullability(), + FullyQualifiedType = p.Type.GetFullyQualifiedNameWithNullability(), Direction = ParameterDirection.In }).ToImmutableArray() ), @@ -513,8 +513,8 @@ private static MockEventModel CreateEventModel(IEventSymbol evt, string? explici raiseParams.Select(p => new MockParameterModel { Name = EscapeIdentifier(p.Name), - FullyQualifiedType = p.Type.GetFullyQualifiedName(), - Type = p.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + FullyQualifiedType = p.Type.GetFullyQualifiedNameWithNullability(), + Type = p.Type.GetMinimallyQualifiedNameWithNullability(), Direction = ParameterDirection.In }).ToImmutableArray()); diff --git a/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs b/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs index ae71c1953f..7114cd0a62 100644 --- a/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs +++ b/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs @@ -54,7 +54,7 @@ public static string GetParameterList(this IMethodSymbol method) RefKind.In => "in ", _ => "" }; - return $"{direction}{p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {p.Name}"; + return $"{direction}{p.Type.GetFullyQualifiedNameWithNullability()} {p.Name}"; })); } diff --git a/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs b/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs index 8643508e76..233641f6a7 100644 --- a/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs +++ b/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs @@ -11,6 +11,26 @@ public static string GetFullyQualifiedName(this ITypeSymbol type) return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); } + public static string GetFullyQualifiedNameWithNullability(this ITypeSymbol type) + { + var name = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + if (type.NullableAnnotation == NullableAnnotation.Annotated && !type.IsValueType) + { + name += "?"; + } + return name; + } + + public static string GetMinimallyQualifiedNameWithNullability(this ITypeSymbol type) + { + var name = type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + if (type.NullableAnnotation == NullableAnnotation.Annotated && !type.IsValueType) + { + name += "?"; + } + return name; + } + public static string GetFullyQualifiedNameWithoutGlobal(this ITypeSymbol type) { var fqn = type.GetFullyQualifiedName(); From 6287658e00a6e97cd389118b161f8e59428b28b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 07:49:54 +0000 Subject: [PATCH 3/4] Address Claude's review: use Roslyn IncludeNullableReferenceTypeModifier, fix return type nullability - Replace manual `+= "?"` with SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier for correct handling of edge cases (generic type params, arrays of nullable types) - Fix return type nullability in MemberDiscovery.cs (methods, properties, indexers) - Fix GetUnwrappedReturnType to preserve nullability for async inner types - Update test to include nullable property and verify nullable return types in snapshot Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/4ee1d018-5836-4f4f-a49c-0417206f80cc --- .../MockGeneratorTests.cs | 1 + ...ble_Reference_Type_Parameters.verified.txt | 34 +++++++++++++------ .../Discovery/MemberDiscovery.cs | 10 +++--- .../Extensions/TypeSymbolExtensions.cs | 30 +++++++--------- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs index 5f4b9ccf3d..e11827464d 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs +++ b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs @@ -427,6 +427,7 @@ public interface IFoo void Bar(object? baz); string? GetValue(string? key, int count); void Process(string nonNull, string? nullable, object? obj); + string? NullableProp { get; set; } } public class TestUsage diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt index 65a507aaa3..6691d2a2ac 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt @@ -44,9 +44,9 @@ namespace TUnit.Mocks.Generated _engine.HandleCall(0, "Bar", new object?[] { baz }); } - public string GetValue(string? key, int count) + public string? GetValue(string? key, int count) { - return _engine.HandleCallWithReturn(1, "GetValue", new object?[] { key, count }, default); + return _engine.HandleCallWithReturn(1, "GetValue", new object?[] { key, count }, default); } public void Process(string nonNull, string? nullable, object? obj) @@ -54,6 +54,12 @@ namespace TUnit.Mocks.Generated _engine.HandleCall(2, "Process", new object?[] { nonNull, nullable, obj }); } + public string? NullableProp + { + get => _engine.HandleCallWithReturn(3, "get_NullableProp", global::System.Array.Empty(), default); + set => _engine.HandleCall(4, "set_NullableProp", new object?[] { value }); + } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] public void RaiseEvent(string eventName, object? args) { @@ -172,6 +178,12 @@ namespace TUnit.Mocks.Generated var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_nonNull.Matcher, __fa_nullable.Matcher, __fa_obj.Matcher }; return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); } + + extension(global::TUnit.Mocks.Mock mock) + { + public global::TUnit.Mocks.PropertyMockCall NullableProp + => new(global::TUnit.Mocks.Mock.GetEngine(mock), 3, 4, "NullableProp", true, true); + } } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] @@ -256,7 +268,7 @@ namespace TUnit.Mocks.Generated private readonly int _memberId; private readonly string _memberName; private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; - private readonly global::System.Lazy> _lazyBuilder; + private readonly global::System.Lazy> _lazyBuilder; internal IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -264,23 +276,23 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + _lazyBuilder = new global::System.Lazy>(() => { var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); _engine.AddSetup(setup); - return new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + return new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); } ); } - private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; /// - public IFoo_GetValue_M1_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } + public IFoo_GetValue_M1_MockCall Returns(string? value) { EnsureSetup().Returns(value); return this; } /// - public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } /// - public IFoo_GetValue_M1_MockCall ReturnsSequentially(params string[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + public IFoo_GetValue_M1_MockCall ReturnsSequentially(params string?[] values) { EnsureSetup().ReturnsSequentially(values); return this; } /// public IFoo_GetValue_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } /// @@ -290,7 +302,7 @@ namespace TUnit.Mocks.Generated /// public IFoo_GetValue_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } /// - public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } /// public IFoo_GetValue_M1_MockCall Throws(global::System.Func exceptionFactory) { EnsureSetup().Throws(exceptionFactory); return this; } /// @@ -303,7 +315,7 @@ namespace TUnit.Mocks.Generated public IFoo_GetValue_M1_MockCall Then() { EnsureSetup().Then(); return this; } /// Configure a typed computed return value using the actual method parameters. - public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) + public IFoo_GetValue_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(args => factory((string?)args[0]!, (int)args[1]!)); return this; diff --git a/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs b/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs index 3590e84a50..7a1c5536ee 100644 --- a/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs +++ b/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs @@ -307,7 +307,7 @@ private static MockMemberModel CreateMethodModel(IMethodSymbol method, ref int m { Name = method.Name, MemberId = memberIdCounter++, - ReturnType = returnType.GetFullyQualifiedName(), + ReturnType = returnType.GetFullyQualifiedNameWithNullability(), UnwrappedReturnType = unwrappedType, IsVoid = isVoid, IsAsync = isAsync, @@ -380,8 +380,8 @@ private static MockMemberModel CreatePropertyModel(IPropertySymbol property, ref { Name = property.Name, MemberId = getterId, - ReturnType = property.Type.GetFullyQualifiedName(), - UnwrappedReturnType = property.Type.GetFullyQualifiedName(), + ReturnType = property.Type.GetFullyQualifiedNameWithNullability(), + UnwrappedReturnType = property.Type.GetFullyQualifiedNameWithNullability(), IsVoid = false, IsAsync = false, IsProperty = true, @@ -443,8 +443,8 @@ private static MockMemberModel CreateIndexerModel(IPropertySymbol indexer, ref i Name = "this", MemberId = getterId, SetterMemberId = setterId, - ReturnType = indexer.Type.GetFullyQualifiedName(), - UnwrappedReturnType = indexer.Type.GetFullyQualifiedName(), + ReturnType = indexer.Type.GetFullyQualifiedNameWithNullability(), + UnwrappedReturnType = indexer.Type.GetFullyQualifiedNameWithNullability(), IsVoid = false, IsAsync = false, IsProperty = true, diff --git a/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs b/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs index 233641f6a7..317746efcf 100644 --- a/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs +++ b/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs @@ -6,30 +6,24 @@ namespace TUnit.Mocks.SourceGenerator.Extensions; internal static class TypeSymbolExtensions { + private static readonly SymbolDisplayFormat FullyQualifiedWithNullability = + SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + + private static readonly SymbolDisplayFormat MinimallyQualifiedWithNullability = + SymbolDisplayFormat.MinimallyQualifiedFormat.AddMiscellaneousOptions( + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + public static string GetFullyQualifiedName(this ITypeSymbol type) { return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); } public static string GetFullyQualifiedNameWithNullability(this ITypeSymbol type) - { - var name = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - if (type.NullableAnnotation == NullableAnnotation.Annotated && !type.IsValueType) - { - name += "?"; - } - return name; - } + => type.ToDisplayString(FullyQualifiedWithNullability); public static string GetMinimallyQualifiedNameWithNullability(this ITypeSymbol type) - { - var name = type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); - if (type.NullableAnnotation == NullableAnnotation.Annotated && !type.IsValueType) - { - name += "?"; - } - return name; - } + => type.ToDisplayString(MinimallyQualifiedWithNullability); public static string GetFullyQualifiedNameWithoutGlobal(this ITypeSymbol type) { @@ -217,9 +211,9 @@ public static (string UnwrappedType, bool IsVoidAsync) GetUnwrappedReturnType(th var name = named.ConstructedFrom.Name; if ((name == "Task" || name == "ValueTask") && named.TypeArguments.Length == 1) { - return (named.TypeArguments[0].GetFullyQualifiedName(), false); + return (named.TypeArguments[0].GetFullyQualifiedNameWithNullability(), false); } } - return (type.GetFullyQualifiedName(), false); + return (type.GetFullyQualifiedNameWithNullability(), false); } } From c117b6121eae0da770bbd1a1b6402722482c80d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:14:45 +0000 Subject: [PATCH 4/4] Address review: add discoverability comment and Task test coverage - Add XML doc comment to GetFullyQualifiedName() directing future contributors to use GetFullyQualifiedNameWithNullability() for code generation - Add Task GetAsync(string? key) to nullable test interface to cover async unwrapping edge case in GetUnwrappedReturnType - Update snapshot with generated async method preserving string? nullability Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> Agent-Logs-Url: https://github.com/thomhurst/TUnit/sessions/78103138-e1f5-46d0-a1fb-9124fc5f8906 --- .../MockGeneratorTests.cs | 2 + ...ble_Reference_Type_Parameters.verified.txt | 121 +++++++++++++++++- .../Extensions/TypeSymbolExtensions.cs | 5 + 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs index e11827464d..e74f80f115 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs +++ b/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs @@ -421,12 +421,14 @@ public Task Interface_With_Nullable_Reference_Type_Parameters() { var source = """ using TUnit.Mocks; + using System.Threading.Tasks; public interface IFoo { void Bar(object? baz); string? GetValue(string? key, int count); void Process(string nonNull, string? nullable, object? obj); + Task GetAsync(string? key); string? NullableProp { get; set; } } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt index 6691d2a2ac..1b42ee5f1b 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt @@ -54,10 +54,23 @@ namespace TUnit.Mocks.Generated _engine.HandleCall(2, "Process", new object?[] { nonNull, nullable, obj }); } + public global::System.Threading.Tasks.Task GetAsync(string? key) + { + try + { + var __result = _engine.HandleCallWithReturn(3, "GetAsync", new object?[] { key }, default); + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + public string? NullableProp { - get => _engine.HandleCallWithReturn(3, "get_NullableProp", global::System.Array.Empty(), default); - set => _engine.HandleCall(4, "set_NullableProp", new object?[] { value }); + get => _engine.HandleCallWithReturn(4, "get_NullableProp", global::System.Array.Empty(), default); + set => _engine.HandleCall(5, "set_NullableProp", new object?[] { value }); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] @@ -179,10 +192,23 @@ namespace TUnit.Mocks.Generated return new IFoo_Process_M2_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 2, "Process", matchers); } + public static IFoo_GetAsync_M3_MockCall GetAsync(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg key) + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { key.Matcher }; + return new IFoo_GetAsync_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "GetAsync", matchers); + } + + public static IFoo_GetAsync_M3_MockCall GetAsync(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 IFoo_GetAsync_M3_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 3, "GetAsync", matchers); + } + extension(global::TUnit.Mocks.Mock mock) { public global::TUnit.Mocks.PropertyMockCall NullableProp - => new(global::TUnit.Mocks.Mock.GetEngine(mock), 3, 4, "NullableProp", true, true); + => new(global::TUnit.Mocks.Mock.GetEngine(mock), 4, 5, "NullableProp", true, true); } } @@ -424,4 +450,93 @@ namespace TUnit.Mocks.Generated /// public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IFoo_GetAsync_M3_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private readonly global::System.Lazy> _lazyBuilder; + + internal IFoo_GetAsync_M3_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + _lazyBuilder = new global::System.Lazy>(() => + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + _engine.AddSetup(setup); + return new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + } + ); + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; + + /// + public IFoo_GetAsync_M3_MockCall Returns(string? value) { EnsureSetup().Returns(value); return this; } + /// + public IFoo_GetAsync_M3_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IFoo_GetAsync_M3_MockCall ReturnsSequentially(params string?[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IFoo_GetAsync_M3_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IFoo_GetAsync_M3_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IFoo_GetAsync_M3_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_GetAsync_M3_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IFoo_GetAsync_M3_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IFoo_GetAsync_M3_MockCall Throws(global::System.Func exceptionFactory) { EnsureSetup().Throws(exceptionFactory); return this; } + /// + public IFoo_GetAsync_M3_MockCall Raises(string eventName, object? args = null) { EnsureSetup().Raises(eventName, args); return this; } + /// + public IFoo_GetAsync_M3_MockCall SetsOutParameter(int paramIndex, object? value) { EnsureSetup().SetsOutParameter(paramIndex, value); return this; } + /// + public IFoo_GetAsync_M3_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IFoo_GetAsync_M3_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Configure a typed computed return value using the actual method parameters. + public IFoo_GetAsync_M3_MockCall Returns(global::System.Func factory) + { + EnsureSetup().Returns(args => factory((string?)args[0]!)); + return this; + } + + /// Execute a typed callback using the actual method parameters. + public IFoo_GetAsync_M3_MockCall Callback(global::System.Action callback) + { + EnsureSetup().Callback(args => callback((string?)args[0]!)); + return this; + } + + /// Configure a typed computed exception using the actual method parameters. + public IFoo_GetAsync_M3_MockCall Throws(global::System.Func exceptionFactory) + { + EnsureSetup().Throws(args => exceptionFactory((string?)args[0]!)); + return this; + } + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } } diff --git a/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs b/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs index 317746efcf..3bcd83fdd8 100644 --- a/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs +++ b/TUnit.Mocks.SourceGenerator/Extensions/TypeSymbolExtensions.cs @@ -14,6 +14,11 @@ internal static class TypeSymbolExtensions SymbolDisplayFormat.MinimallyQualifiedFormat.AddMiscellaneousOptions( SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + /// + /// Returns the fully qualified name without nullable annotations. + /// For code generation that must preserve nullable reference type annotations, + /// use instead. + /// public static string GetFullyQualifiedName(this ITypeSymbol type) { return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);