diff --git a/.claude/docs/project-structure.md b/.claude/docs/project-structure.md index d9042081aa..541a8089e9 100644 --- a/.claude/docs/project-structure.md +++ b/.claude/docs/project-structure.md @@ -50,6 +50,24 @@ | `TUnit.PublicAPI` | Snapshot tests for public API | | `TUnit.Templates.Tests` | Template instantiation tests | +## Mocking Framework + +| Project | Purpose | +|---------|---------| +| `TUnit.Mocks` | Source-generated mocking framework (core runtime: `MockEngine`, `Mock`, setup/verification) | +| `TUnit.Mocks.SourceGenerator` | Roslyn source generator that emits mock implementations and extension methods | +| `TUnit.Mocks.SourceGenerator.Roslyn414/44/47` | Roslyn version variants (link source from base generator) | +| `TUnit.Mocks.Analyzers` | Analyzers for mock usage correctness | +| `TUnit.Mocks.Assertions` | TUnit assertion integration for mocks | +| `TUnit.Mocks.Http` | HTTP mocking (`MockHttpHandler`, `MockHttpClient`) | +| `TUnit.Mocks.Logging` | `ILogger` mocking (`MockLogger`, `MockLogger`) | +| `TUnit.Mocks.Tests` | Runtime mock tests (672 tests) | +| `TUnit.Mocks.SourceGenerator.Tests` | Snapshot tests for generated code | +| `TUnit.Mocks.Analyzers.Tests` | Analyzer tests | +| `TUnit.Mocks.Http.Tests` | HTTP mock tests | +| `TUnit.Mocks.Logging.Tests` | Logging mock tests | +| `TUnit.Mocks.Benchmarks` | BenchmarkDotNet performance comparisons vs Moq/NSubstitute/FakeItEasy | + ## Performance & Benchmarking | Project | Purpose | 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 aae5a4002c..ef90b8fdde 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 @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IReadWriter_MockImpl : global::IReadWriter, global::TUnit.Mocks.IRaisable + internal sealed class IReadWriter_MockImpl : global::IReadWriter, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IReadWriter_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -105,7 +108,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IReadWriter_Write_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -113,17 +118,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IReadWriter_Write_M2_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } 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 8df17a1351..6dd209e2bc 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 @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IAsyncService_MockImpl : global::IAsyncService, global::TUnit.Mocks.IRaisable + internal sealed class IAsyncService_MockImpl : global::IAsyncService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IAsyncService_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -162,7 +165,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IAsyncService_GetValueAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -170,16 +175,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IAsyncService_GetValueAsync_M0_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } @@ -251,7 +255,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IAsyncService_ComputeAsync_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -259,16 +265,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IAsyncService_ComputeAsync_M2_MockCall Returns(int value) { EnsureSetup().Returns(value); return this; } @@ -340,7 +345,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IAsyncService_InitializeAsync_M3_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -348,17 +355,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IAsyncService_InitializeAsync_M3_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } 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 7040397997..9b5534c09c 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -61,10 +61,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class INotifier_MockImpl : global::INotifier, global::TUnit.Mocks.IRaisable + internal sealed class INotifier_MockImpl : global::INotifier, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal INotifier_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -140,7 +143,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal INotifier_Notify_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -148,17 +153,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 INotifier_Notify_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } 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 68f87ae580..34307f8987 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 @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IRepository_MockImpl : global::IRepository, global::TUnit.Mocks.IRaisable + internal sealed class IRepository_MockImpl : global::IRepository, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IRepository_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt index dc8c623623..60df228ce1 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Inherited_Static_Abstract_Members.verified.txt @@ -63,10 +63,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IMyService_MockImpl : global::TUnit.Mocks.Generated.IMyService_Mockable, global::TUnit.Mocks.IRaisable + internal sealed class IMyService_MockImpl : global::TUnit.Mocks.Generated.IMyService_Mockable, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IMyService_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt index ccc0455c03..8851539fb3 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class ITest_MockImpl : global::ITest, global::TUnit.Mocks.IRaisable + internal sealed class ITest_MockImpl : global::ITest, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal ITest_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -116,7 +119,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal ITest_Test_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -124,17 +129,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 ITest_Test_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } @@ -191,7 +195,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal ITest_Get_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -199,16 +205,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 ITest_Get_M1_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } 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 4b83f966ab..8e56e6d8d4 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 @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -61,10 +61,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IService_MockImpl : global::IService, global::TUnit.Mocks.IRaisable + internal sealed class IService_MockImpl : global::IService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IService_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -186,7 +189,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IService_GetAsync_M3_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -194,16 +199,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IService_GetAsync_M3_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } @@ -278,7 +282,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IService_Process_M4_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -286,17 +292,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IService_Process_M4_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } 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 94d54e07ad..c55ec140c1 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 @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IFoo_MockImpl : global::IFoo, global::TUnit.Mocks.IRaisable + internal sealed class IFoo_MockImpl : global::IFoo, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IFoo_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -219,7 +222,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFoo_Bar_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -227,17 +232,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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; } @@ -294,7 +298,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFoo_GetValue_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -302,16 +308,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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; } @@ -383,7 +388,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFoo_Process_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -391,17 +398,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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; } @@ -458,7 +464,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFoo_GetAsync_M3_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -466,16 +474,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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; } 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 fdb45bb5ea..c2d2bbb4fe 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 @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IDictionary_MockImpl : global::IDictionary, global::TUnit.Mocks.IRaisable + internal sealed class IDictionary_MockImpl : global::IDictionary, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IDictionary_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -107,7 +110,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IDictionary_TryGetValue_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -115,16 +120,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IDictionary_TryGetValue_M0_MockCall Returns(bool value) { EnsureSetup().Returns(value); return this; } @@ -200,7 +204,9 @@ 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 global::TUnit.Mocks.Setup.VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IDictionary_Swap_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -208,17 +214,16 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy(() => + _ = EnsureSetup(); + } + + private global::TUnit.Mocks.Setup.VoidMethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IDictionary_Swap_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } 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 cc74b2811e..7198477d00 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IFormatter_MockImpl : global::IFormatter, global::TUnit.Mocks.IRaisable + internal sealed class IFormatter_MockImpl : global::IFormatter, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IFormatter_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -199,7 +202,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFormatter_Format_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -207,16 +212,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IFormatter_Format_M0_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } @@ -288,7 +292,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFormatter_Format_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -296,16 +302,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IFormatter_Format_M1_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } @@ -377,7 +382,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFormatter_Format_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -385,16 +392,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IFormatter_Format_M2_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } @@ -466,7 +472,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IFormatter_Format_M3_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -474,16 +482,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IFormatter_Format_M3_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt index 75d2bee750..1408e0e6d0 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Properties.verified.txt @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IRepository_MockImpl : global::IRepository, global::TUnit.Mocks.IRaisable + internal sealed class IRepository_MockImpl : global::IRepository, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IRepository_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt index 4603818ba5..47ff789005 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_RefStruct_Parameters.verified.txt @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IBufferProcessor_MockImpl : global::IBufferProcessor, global::TUnit.Mocks.IRaisable + internal sealed class IBufferProcessor_MockImpl : global::IBufferProcessor, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IBufferProcessor_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt index 9a09752e23..4f298aeab3 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Members.verified.txt @@ -79,10 +79,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IServiceFactory_MockImpl : global::TUnit.Mocks.Generated.IServiceFactory_Mockable, global::TUnit.Mocks.IRaisable + internal sealed class IServiceFactory_MockImpl : global::TUnit.Mocks.Generated.IServiceFactory_Mockable, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IServiceFactory_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt index bee541a03d..2545746aa2 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IMyService_MockImpl : global::IMyService, global::TUnit.Mocks.IRaisable + internal sealed class IMyService_MockImpl : global::IMyService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IMyService_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -100,7 +103,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IMyService_GetValue_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -108,16 +113,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IMyService_GetValue_M0_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } 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 1b4c77e37f..f7429cf72c 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class ICalculator_MockImpl : global::ICalculator, global::TUnit.Mocks.IRaisable + internal sealed class ICalculator_MockImpl : global::ICalculator, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal ICalculator_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -142,7 +145,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal ICalculator_Add_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -150,16 +155,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 ICalculator_Add_M0_MockCall Returns(int value) { EnsureSetup().Returns(value); return this; } @@ -231,7 +235,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal ICalculator_Subtract_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -239,16 +245,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 ICalculator_Subtract_M1_MockCall Returns(int value) { EnsureSetup().Returns(value); return this; } 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 4ed109f3f2..dbb7feef84 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt @@ -1,4 +1,4 @@ -// +// #nullable enable namespace TUnit.Mocks.Generated @@ -30,10 +30,13 @@ namespace TUnit.Mocks.Generated namespace TUnit.Mocks.Generated { - internal sealed class IGreeter_MockImpl : global::IGreeter, global::TUnit.Mocks.IRaisable + internal sealed class IGreeter_MockImpl : global::IGreeter, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject { private readonly global::TUnit.Mocks.MockEngine _engine; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + internal IGreeter_MockImpl(global::TUnit.Mocks.MockEngine engine) { _engine = engine; @@ -83,7 +86,9 @@ 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 global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; internal IGreeter_Greet_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) { @@ -91,16 +96,15 @@ namespace TUnit.Mocks.Generated _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new global::System.Lazy>(() => + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() => + global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { 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 IGreeter_Greet_M0_MockCall Returns(string value) { EnsureSetup().Returns(value); return this; } diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs index 518deccb33..99c83d7e4b 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs @@ -45,11 +45,13 @@ private static void BuildInterfaceMockImpl(CodeWriter writer, MockTypeModel mode baseTypes += ", " + string.Join(", ", model.AdditionalInterfaceNames); } - using (writer.Block($"internal sealed class {safeName}_MockImpl : {baseTypes}, global::TUnit.Mocks.IRaisable")) + using (writer.Block($"internal sealed class {safeName}_MockImpl : {baseTypes}, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject")) { writer.AppendLine($"private readonly global::TUnit.Mocks.MockEngine<{mockableType}> _engine;"); writer.AppendLine(); + EmitIMockObjectProperty(writer); + // Constructor using (writer.Block($"internal {safeName}_MockImpl(global::TUnit.Mocks.MockEngine<{mockableType}> engine)")) { @@ -95,12 +97,14 @@ private static void BuildWrapMockImpl(CodeWriter writer, MockTypeModel model, st { var mockableType = GetMockableTypeName(model); - using (writer.Block($"internal sealed class {safeName}_WrapMockImpl : {model.FullyQualifiedName}, global::TUnit.Mocks.IRaisable")) + using (writer.Block($"internal sealed class {safeName}_WrapMockImpl : {model.FullyQualifiedName}, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject")) { writer.AppendLine($"private readonly global::TUnit.Mocks.MockEngine<{mockableType}> _engine;"); writer.AppendLine($"private readonly {model.FullyQualifiedName} _wrappedInstance;"); writer.AppendLine(); + EmitIMockObjectProperty(writer); + // Generate constructors that pass through to base + accept wrapped instance GenerateWrapConstructors(writer, model, safeName); @@ -421,11 +425,13 @@ private static void BuildPartialMockImpl(CodeWriter writer, MockTypeModel model, { var mockableType = GetMockableTypeName(model); - using (writer.Block($"internal sealed class {safeName}_MockImpl : {model.FullyQualifiedName}, global::TUnit.Mocks.IRaisable")) + using (writer.Block($"internal sealed class {safeName}_MockImpl : {model.FullyQualifiedName}, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject")) { writer.AppendLine($"private readonly global::TUnit.Mocks.MockEngine<{mockableType}> _engine;"); writer.AppendLine(); + EmitIMockObjectProperty(writer); + // Generate constructors that pass through to base GeneratePartialConstructors(writer, model, safeName); @@ -1268,6 +1274,13 @@ public static string GetMockableTypeName(MockTypeModel model) /// Emits the static engine assignment with a guard that detects multiple mocks of the same /// static-abstract interface type within a single test context. /// + private static void EmitIMockObjectProperty(CodeWriter writer) + { + writer.AppendLine("[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]"); + writer.AppendLine("global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; }"); + writer.AppendLine(); + } + internal static void EmitStaticEngineAssignment(CodeWriter writer, string safeName) { writer.AppendLine($"if ({safeName}_StaticEngine.Engine is not null)"); diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs index 2de65e25f4..c3339ed0a2 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs @@ -153,10 +153,12 @@ private static void GenerateReturnUnifiedClass(CodeWriter writer, string wrapper writer.AppendLine("private readonly int _memberId;"); writer.AppendLine("private readonly string _memberName;"); writer.AppendLine("private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers;"); - writer.AppendLine($"private readonly global::System.Lazy<{builderType}> _lazyBuilder;"); + writer.AppendLine($"private {builderType}? _builder;"); + writer.AppendLine("private bool _builderInitialized;"); + writer.AppendLine("private object? _builderLock;"); writer.AppendLine(); - // Constructor — lazy registration via Lazy for thread safety + // Constructor writer.AppendLine($"internal {wrapperName}(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers)"); using (writer.Block()) { @@ -164,19 +166,12 @@ private static void GenerateReturnUnifiedClass(CodeWriter writer, string wrapper writer.AppendLine("_memberId = memberId;"); writer.AppendLine("_memberName = memberName;"); writer.AppendLine("_matchers = matchers;"); - using (writer.Block($"_lazyBuilder = new global::System.Lazy<{builderType}>(() =>")) - { - writer.AppendLine("var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName);"); - writer.AppendLine("_engine.AddSetup(setup);"); - writer.AppendLine($"return new {builderType}(setup);"); - } - writer.AppendLine(");"); } writer.AppendLine(); - // EnsureSetup method - writer.AppendLine($"private {builderType} EnsureSetup() => _lazyBuilder.Value;"); + // EnsureSetup method — uses LazyInitializer to guarantee exactly-once AddSetup + EmitEnsureSetup(writer, builderType); writer.AppendLine(); @@ -290,10 +285,12 @@ private static void GenerateVoidUnifiedClass(CodeWriter writer, string wrapperNa writer.AppendLine("private readonly int _memberId;"); writer.AppendLine("private readonly string _memberName;"); writer.AppendLine("private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers;"); - writer.AppendLine($"private readonly global::System.Lazy<{builderType}> _lazyBuilder;"); + writer.AppendLine($"private {builderType}? _builder;"); + writer.AppendLine("private bool _builderInitialized;"); + writer.AppendLine("private object? _builderLock;"); writer.AppendLine(); - // Constructor — Lazy for thread safety, eagerly materialized because void methods + // Constructor — eagerly registers because void methods // are commonly used without chaining (e.g., mock.Log(Arg.Any()) in strict mode). writer.AppendLine($"internal {wrapperName}(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers)"); using (writer.Block()) @@ -302,20 +299,13 @@ private static void GenerateVoidUnifiedClass(CodeWriter writer, string wrapperNa writer.AppendLine("_memberId = memberId;"); writer.AppendLine("_memberName = memberName;"); writer.AppendLine("_matchers = matchers;"); - using (writer.Block($"_lazyBuilder = new global::System.Lazy<{builderType}>(() =>")) - { - writer.AppendLine("var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName);"); - writer.AppendLine("_engine.AddSetup(setup);"); - writer.AppendLine($"return new {builderType}(setup);"); - } - writer.AppendLine(");"); - writer.AppendLine("_ = _lazyBuilder.Value;"); + writer.AppendLine("_ = EnsureSetup();"); } writer.AppendLine(); - // EnsureSetup method - writer.AppendLine($"private {builderType} EnsureSetup() => _lazyBuilder.Value;"); + // EnsureSetup method — uses LazyInitializer to guarantee exactly-once AddSetup + EmitEnsureSetup(writer, builderType); writer.AppendLine(); @@ -869,4 +859,17 @@ private static string GetArgParameterList(MockMemberModel method, bool includeRe return string.Join(", ", parts); } + private static void EmitEnsureSetup(CodeWriter writer, string builderType) + { + writer.AppendLine($"private {builderType} EnsureSetup() =>"); + writer.IncreaseIndent(); + writer.AppendLine($"global::System.Threading.LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () =>"); + writer.OpenBrace(); + writer.AppendLine("var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName);"); + writer.AppendLine("_engine.AddSetup(setup);"); + writer.AppendLine($"return new {builderType}(setup);"); + writer.CloseBrace(")!;"); + writer.DecreaseIndent(); + } + } diff --git a/TUnit.Mocks/IMockObject.cs b/TUnit.Mocks/IMockObject.cs new file mode 100644 index 0000000000..214e94ea20 --- /dev/null +++ b/TUnit.Mocks/IMockObject.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace TUnit.Mocks; + +/// +/// Implemented by generated mock impl classes to allow reverse-lookup +/// from a mock object to its wrapper without using +/// ConditionalWeakTable. Not intended for direct use. +/// +/// +/// This interface is public because it must be implemented by source-generated code +/// in the user's assembly. The setter is required because the wrapper is assigned by +/// 's constructor after the impl object is created. +/// hides it from IDE autocompletion. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public interface IMockObject +{ + /// + /// The wrapper for this mock object. + /// Set once by the constructor; should not be reassigned. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + IMock? MockWrapper { get; set; } +} diff --git a/TUnit.Mocks/Mock.cs b/TUnit.Mocks/Mock.cs index 132c7eb29c..2553ea2e6d 100644 --- a/TUnit.Mocks/Mock.cs +++ b/TUnit.Mocks/Mock.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Runtime.CompilerServices; using TUnit.Mocks.Diagnostics; using TUnit.Mocks.Verification; @@ -10,10 +9,6 @@ namespace TUnit.Mocks; /// public static class Mock { - // Maps mock implementation objects back to their Mock wrappers. - // ConditionalWeakTable so mocks can be GC'd normally. - private static readonly ConditionalWeakTable _objectToMock = new(); - // The source generator registers factories via this method at module initialization time. // ConcurrentDictionary is used because module initializers from multiple assemblies // can run concurrently when test assemblies are loaded in parallel. @@ -31,21 +26,6 @@ public static class Mock // Separate registry for wrap mock factories that accept a real instance. private static readonly ConcurrentDictionary> _wrapFactories = new(); - /// - /// Registers the mapping from a mock implementation object to its wrapper. - /// Called from the constructor. Not intended for direct use. - /// - internal static void Register(object mockObject, IMock mockWrapper) - { -#if NET7_0_OR_GREATER - _objectToMock.AddOrUpdate(mockObject, mockWrapper); -#else - // ConditionalWeakTable.AddOrUpdate not available before .NET 7. - // Each mock object is unique (created by new), so Add will not throw. - _objectToMock.Add(mockObject, mockWrapper); -#endif - } - /// /// Retrieves the wrapper for a mock implementation object. /// Use this to access the mock wrapper from auto-mocked return values or any mocked object. @@ -60,13 +40,15 @@ internal static void Register(object mockObject, IMock mockWrapper) /// public static Mock Get(T mockedObject) where T : class { - if (_objectToMock.TryGetValue(mockedObject, out var mock)) + if (mockedObject is IMockObject { MockWrapper: { } wrapper }) { - if (mock is Mock typed) + if (wrapper is Mock typed) + { return typed; + } throw new InvalidOperationException( - $"The object is a mock of '{mock.GetType().GenericTypeArguments[0].Name}', not '{typeof(T).Name}'."); + $"The object is a mock of '{wrapper.GetType().GenericTypeArguments[0].Name}', not '{typeof(T).Name}'."); } throw new InvalidOperationException( @@ -81,7 +63,7 @@ public static Mock Get(T mockedObject) where T : class [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static void RegisterFactory(Func> factory) where T : class { - _factories[typeof(T)] = behavior => factory(behavior); + _factories[typeof(T)] = factory; } /// @@ -91,7 +73,7 @@ public static void RegisterFactory(Func> factory) where [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static void RegisterPartialFactory(Func> factory) where T : class { - _partialFactories[typeof(T)] = (behavior, args) => factory(behavior, args); + _partialFactories[typeof(T)] = factory; } /// @@ -111,7 +93,7 @@ public static void RegisterMultiFactory(string key, Func f [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static void RegisterDelegateFactory(Func> factory) where T : class { - _delegateFactories[typeof(T)] = behavior => factory(behavior); + _delegateFactories[typeof(T)] = factory; } /// diff --git a/TUnit.Mocks/MockEngine.cs b/TUnit.Mocks/MockEngine.cs index b431cd82ab..2e69abad53 100644 --- a/TUnit.Mocks/MockEngine.cs +++ b/TUnit.Mocks/MockEngine.cs @@ -22,9 +22,9 @@ internal static class MockCallSequence [EditorBrowsable(EditorBrowsableState.Never)] public sealed class MockEngine : IMockEngineAccess where T : class { - private readonly Dictionary> _setupsByMember = new(); private readonly Lock _setupLock = new(); - private readonly ConcurrentQueue _callHistory = new(); + private Dictionary>? _setupsByMember; + private ConcurrentQueue? _callHistory; private ConcurrentDictionary? _autoTrackValues; private ConcurrentQueue<(string EventName, bool IsSubscribe)>? _eventSubscriptions; @@ -119,9 +119,11 @@ public void AddSetup(MethodSetup setup) setup.RequiredState = PendingRequiredState; } - if (!_setupsByMember.TryGetValue(setup.MemberId, out var list)) + var dict = _setupsByMember ??= new(); + + if (!dict.TryGetValue(setup.MemberId, out var list)) { - _setupsByMember[setup.MemberId] = list = new(); + dict[setup.MemberId] = list = new(); } list.Add(setup); @@ -384,8 +386,13 @@ public bool TryHandleCallWithReturn(int memberId, string memberName, ob /// public IReadOnlyList GetCallsFor(int memberId) { + if (Volatile.Read(ref _callHistory) is not { } history) + { + return []; + } + var result = new List(); - foreach (var record in _callHistory) + foreach (var record in history) { if (record.MemberId == memberId) { @@ -400,7 +407,7 @@ public IReadOnlyList GetCallsFor(int memberId) /// public IReadOnlyList GetAllCalls() { - return _callHistory.ToArray(); + return Volatile.Read(ref _callHistory)?.ToArray() ?? []; } /// @@ -409,8 +416,13 @@ public IReadOnlyList GetAllCalls() [EditorBrowsable(EditorBrowsableState.Never)] public IReadOnlyList GetUnverifiedCalls() { + if (Volatile.Read(ref _callHistory) is not { } history) + { + return []; + } + var result = new List(); - foreach (var record in _callHistory) + foreach (var record in history) { if (!record.IsVerified) { @@ -428,8 +440,13 @@ public IReadOnlyList GetSetups() { lock (_setupLock) { + if (_setupsByMember is not { } setups) + { + return []; + } + var all = new List(); - foreach (var list in _setupsByMember.Values) + foreach (var list in setups.Values) { all.AddRange(list); } @@ -465,11 +482,14 @@ public Diagnostics.MockDiagnostics GetDiagnostics() } var unmatchedCalls = new List(); - foreach (var call in _callHistory) + if (Volatile.Read(ref _callHistory) is { } history) { - if (call.IsUnmatched) + foreach (var call in history) { - unmatchedCalls.Add(call); + if (call.IsUnmatched) + { + unmatchedCalls.Add(call); + } } } @@ -497,14 +517,12 @@ public void Reset() { lock (_setupLock) { - _setupsByMember.Clear(); + _setupsByMember = null; _currentState = null; PendingRequiredState = null; } - // Drain the queue - while (_callHistory.TryDequeue(out _)) { } - + Volatile.Write(ref _callHistory, null); Volatile.Write(ref _autoTrackValues, null); Volatile.Write(ref _eventSubscriptions, null); Volatile.Write(ref _onSubscribeCallbacks, null); @@ -591,7 +609,7 @@ private CallRecord RecordCall(int memberId, string memberName, object?[] args) { var seq = MockCallSequence.Next(); var record = new CallRecord(memberId, memberName, args, seq); - _callHistory.Enqueue(record); + LazyInitializer.EnsureInitialized(ref _callHistory)!.Enqueue(record); return record; } @@ -610,7 +628,7 @@ private void RaiseEventsForSetup(MethodSetup setup) { lock (_setupLock) { - if (!_setupsByMember.TryGetValue(memberId, out var setups)) + if (_setupsByMember is not { } setupDict || !setupDict.TryGetValue(memberId, out var setups)) { return (false, null, null); } diff --git a/TUnit.Mocks/MockMethodCall.cs b/TUnit.Mocks/MockMethodCall.cs index f624192f7f..0faeac9627 100644 --- a/TUnit.Mocks/MockMethodCall.cs +++ b/TUnit.Mocks/MockMethodCall.cs @@ -20,7 +20,9 @@ public sealed class MockMethodCall : IMethodSetup, ISetupChain private readonly int _memberId; private readonly string _memberName; private readonly IArgumentMatcher[] _matchers; - private readonly Lazy> _lazyBuilder; + private MethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; [EditorBrowsable(EditorBrowsableState.Never)] public MockMethodCall(IMockEngineAccess engine, int memberId, string memberName, IArgumentMatcher[] matchers) @@ -29,15 +31,15 @@ public MockMethodCall(IMockEngineAccess engine, int memberId, string memberName, _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new Lazy>(() => + } + + private MethodSetupBuilder EnsureSetup() => + LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => { var setup = new MethodSetup(_memberId, _matchers, _memberName); _engine.AddSetup(setup); return new MethodSetupBuilder(setup); - }); - } - - private MethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; + })!; // IMethodSetup implementation diff --git a/TUnit.Mocks/MockOfT.cs b/TUnit.Mocks/MockOfT.cs index 4f9550ca11..8675f9179c 100644 --- a/TUnit.Mocks/MockOfT.cs +++ b/TUnit.Mocks/MockOfT.cs @@ -27,7 +27,10 @@ public Mock(T mockObject, MockEngine engine) { _engine = engine; Object = mockObject; - Mock.Register(mockObject, this); + if (mockObject is IMockObject mockObj) + { + mockObj.MockWrapper = this; + } } /// diff --git a/TUnit.Mocks/Setup/MethodSetup.cs b/TUnit.Mocks/Setup/MethodSetup.cs index ac52557340..0370b2f8c2 100644 --- a/TUnit.Mocks/Setup/MethodSetup.cs +++ b/TUnit.Mocks/Setup/MethodSetup.cs @@ -11,12 +11,15 @@ namespace TUnit.Mocks.Setup; public sealed class MethodSetup { private readonly IArgumentMatcher[] _matchers; - private readonly Lock _behaviorLock = new(); - private readonly List _behaviors = new(); - private readonly List _eventRaises = new(); + private Lock? _behaviorLock; + private List? _behaviors; + private List? _eventRaises; + private EventRaiseInfo[]? _eventRaisesSnapshot; private Dictionary? _outRefAssignments; private int _callIndex; + private Lock EnsureBehaviorLock() => LazyInitializer.EnsureInitialized(ref _behaviorLock)!; + public int MemberId { get; } /// @@ -59,9 +62,10 @@ public MethodSetup(int memberId, IArgumentMatcher[] matchers, string memberName public void AddBehavior(IBehavior behavior) { - lock (_behaviorLock) + lock (EnsureBehaviorLock()) { - _behaviors.Add(behavior); + var list = _behaviors ??= new(); + list.Add(behavior); } } @@ -80,18 +84,30 @@ public bool Matches(object?[] actualArgs) public void AddEventRaise(EventRaiseInfo raiseInfo) { - lock (_behaviorLock) + lock (EnsureBehaviorLock()) { - _eventRaises.Add(raiseInfo); + var list = _eventRaises ??= new(); + list.Add(raiseInfo); + _eventRaisesSnapshot = null; } } [EditorBrowsable(EditorBrowsableState.Never)] public IReadOnlyList GetEventRaises() { - lock (_behaviorLock) + if (Volatile.Read(ref _eventRaises) is null) + { + return []; + } + + if (Volatile.Read(ref _eventRaisesSnapshot) is { } snapshot) { - return _eventRaises.ToList(); + return snapshot; + } + + lock (EnsureBehaviorLock()) + { + return _eventRaisesSnapshot ??= _eventRaises!.ToArray(); } } @@ -118,7 +134,7 @@ public void ApplyCaptures(object?[] args) /// The value to assign. public void SetOutRefValue(int paramIndex, object? value) { - lock (_behaviorLock) + lock (EnsureBehaviorLock()) { _outRefAssignments ??= new Dictionary(); _outRefAssignments[paramIndex] = value; @@ -133,7 +149,13 @@ public void SetOutRefValue(int paramIndex, object? value) { get { - lock (_behaviorLock) + var lck = Volatile.Read(ref _behaviorLock); + if (lck is null) + { + return null; + } + + lock (lck) { return _outRefAssignments; } @@ -156,15 +178,22 @@ public string[] GetMatcherDescriptions() public IBehavior? GetNextBehavior() { - lock (_behaviorLock) + if (Volatile.Read(ref _behaviors) is null) + { + return null; + } + + lock (EnsureBehaviorLock()) { - if (_behaviors.Count == 0) + if (_behaviors is not { Count: > 0 } behaviors) + { return null; + } var index = _callIndex; if (_callIndex < int.MaxValue) _callIndex++; // Clamp to last behavior (last one repeats) - return _behaviors[Math.Min(index, _behaviors.Count - 1)]; + return behaviors[Math.Min(index, behaviors.Count - 1)]; } } } diff --git a/TUnit.Mocks/VoidMockMethodCall.cs b/TUnit.Mocks/VoidMockMethodCall.cs index 2df33a2ea5..c2bf80012c 100644 --- a/TUnit.Mocks/VoidMockMethodCall.cs +++ b/TUnit.Mocks/VoidMockMethodCall.cs @@ -22,7 +22,9 @@ public sealed class VoidMockMethodCall : IVoidMethodSetup, IVoidSetupChain, ICal private readonly int _memberId; private readonly string _memberName; private readonly IArgumentMatcher[] _matchers; - private readonly Lazy _lazyBuilder; + private VoidMethodSetupBuilder? _builder; + private bool _builderInitialized; + private object? _builderLock; [EditorBrowsable(EditorBrowsableState.Never)] public VoidMockMethodCall(IMockEngineAccess engine, int memberId, string memberName, IArgumentMatcher[] matchers) @@ -37,19 +39,19 @@ internal VoidMockMethodCall(IMockEngineAccess engine, int memberId, string membe _memberId = memberId; _memberName = memberName; _matchers = matchers; - _lazyBuilder = new Lazy(() => - { - var setup = new MethodSetup(_memberId, _matchers, _memberName); - _engine.AddSetup(setup); - return new VoidMethodSetupBuilder(setup); - }); if (eagerRegister) { - _ = _lazyBuilder.Value; + _ = EnsureSetup(); } } - private VoidMethodSetupBuilder EnsureSetup() => _lazyBuilder.Value; + private VoidMethodSetupBuilder EnsureSetup() => + LazyInitializer.EnsureInitialized(ref _builder, ref _builderInitialized, ref _builderLock, () => + { + var setup = new MethodSetup(_memberId, _matchers, _memberName); + _engine.AddSetup(setup); + return new VoidMethodSetupBuilder(setup); + })!; // IVoidMethodSetup implementation