diff --git a/Docs/pages/advanced-features/03-monitor-interactions.md b/Docs/pages/advanced-features/03-monitor-interactions.md index 8386e9c0..2dd0b116 100644 --- a/Docs/pages/advanced-features/03-monitor-interactions.md +++ b/Docs/pages/advanced-features/03-monitor-interactions.md @@ -5,10 +5,10 @@ can use a `MockMonitor`: ```csharp var sut = Mock.Create(); +var monitor = new MockMonitor(sut); sut.Dispense("Dark", 1); // Not monitored -var monitorScope = sut.MonitorMock(out var monitor); -using (monitorScope) +using (monitor.Run()) { sut.Dispense("Dark", 2); // Monitored } @@ -18,6 +18,21 @@ sut.Dispense("Dark", 3); // Not monitored monitor.Verify.Invoked.Dispense(It.Is("Dark"), It.IsAny()).Once(); ``` +Alternatively, you can use the `MonitorMock()` extension method to create an already running monitor directly from the +mock: + +```csharp +var sut = Mock.Create(); + +sut.Dispense("Dark", 1); // Not monitored +using var scope = sut.MonitorMock(out var monitor); +sut.Dispense("Dark", 2); // Monitored +sut.Dispense("Dark", 3); // Monitored + +// Verifications on the monitor only count interactions during the lifetime scope of the `IDisposable` +monitor.Verify.Invoked.Dispense(It.Is("Dark"), It.IsAny()).Twice(); +``` + ## Clear all interactions For simpler scenarios you can directly clear all recorded interactions on a mock using `ClearAllInteractions` on the diff --git a/README.md b/README.md index 3a0ddf3c..7053a80b 100644 --- a/README.md +++ b/README.md @@ -743,10 +743,10 @@ can use a `MockMonitor`: ```csharp var sut = Mock.Create(); +var monitor = new MockMonitor(sut); sut.Dispense("Dark", 1); // Not monitored -var monitorScope = sut.MonitorMock(out var monitor); -using (monitorScope) +using (monitor.Run()) { sut.Dispense("Dark", 2); // Monitored } @@ -756,6 +756,21 @@ sut.Dispense("Dark", 3); // Not monitored monitor.Verify.Invoked.Dispense(It.Is("Dark"), It.IsAny()).Once(); ``` +Alternatively, you can use the `MonitorMock()` extension method to create an already running monitor directly from the +mock: + +```csharp +var sut = Mock.Create(); + +sut.Dispense("Dark", 1); // Not monitored +using var scope = sut.MonitorMock(out var monitor); +sut.Dispense("Dark", 2); // Monitored +sut.Dispense("Dark", 3); // Monitored + +// Verifications on the monitor only count interactions during the lifetime scope of the `IDisposable` +monitor.Verify.Invoked.Dispense(It.Is("Dark"), It.IsAny()).Twice(); +``` + #### Clear all interactions For simpler scenarios you can directly clear all recorded interactions on a mock using `ClearAllInteractions` on the diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs index 5df764d2..aa7fcb40 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs @@ -51,10 +51,10 @@ namespace Mockolate; .Append(@class.ClassFullName.EscapeForXmlDoc()).Append("\" />.").AppendLine(); sb.Append("\t\t/// ").AppendLine(); sb.Append("\t\tpublic System.IDisposable MonitorMock(out MockMonitor<").Append(@class.ClassFullName) - .AppendLine("> monitor)").AppendLine(); + .Append("> monitor)").AppendLine(); sb.Append("\t\t{").AppendLine(); sb.Append("\t\t\tmonitor = new MockMonitor<").Append(@class.ClassFullName) - .AppendLine(">(GetMockOrThrow(subject));").AppendLine(); + .AppendLine(">(subject);").AppendLine(); sb.Append("\t\t\treturn monitor.Run();").AppendLine(); sb.Append("\t\t}").AppendLine(); sb.AppendLine("\t}"); @@ -203,10 +203,10 @@ private static Mock GetMockOrThrow(T subject) where T : System.Delegate .Append(@class.ClassFullName.EscapeForXmlDoc()).Append("\" />.").AppendLine(); sb.Append("\t\t/// ").AppendLine(); sb.Append("\t\tpublic System.IDisposable MonitorMock(out MockMonitor<").Append(@class.ClassFullName) - .AppendLine("> monitor)").AppendLine(); + .Append("> monitor)").AppendLine(); sb.Append("\t\t{").AppendLine(); sb.Append("\t\t\tmonitor = new MockMonitor<").Append(@class.ClassFullName) - .AppendLine(">(GetMockOrThrow(subject));").AppendLine(); + .AppendLine(">(subject);").AppendLine(); sb.Append("\t\t\treturn monitor.Run();").AppendLine(); sb.Append("\t\t}").AppendLine(); sb.AppendLine("\t}"); diff --git a/Source/Mockolate/Monitor/MockMonitor.cs b/Source/Mockolate/Monitor/MockMonitor.cs index a3a3dea6..e9d7d408 100644 --- a/Source/Mockolate/Monitor/MockMonitor.cs +++ b/Source/Mockolate/Monitor/MockMonitor.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Mockolate.Exceptions; using Mockolate.Interactions; using Mockolate.Verify; @@ -100,16 +101,22 @@ private sealed class MonitorScope(Action callback) : IDisposable /// /// /// Use this class to track and analyze interactions with a mock, such as which members were accessed or -/// which events were subscribed to, during a test session. Monitoring is session-based; begin a session with the Run -/// method and dispose the returned scope to finalize monitoring. +/// which events were subscribed to, during a test session.
+/// Monitoring is session-based: begin a session with the method and +/// dispose the returned scope to finalize monitoring. ///
public sealed class MockMonitor : MockMonitor { /// - public MockMonitor(Mock mock) : base(mock.Interactions) + public MockMonitor(T mock) : this(mock as IMockSubject) + { + } + + private MockMonitor(IMockSubject? mockSubject) + : base(mockSubject?.Mock.Interactions ?? throw new MockException("The subject is no mock.")) { - Verify = new Mock(mock.Subject, - new MockRegistration(mock.Registrations.Behavior, mock.Registrations.Prefix, Interactions)); + Verify = new Mock(mockSubject.Mock.Subject, + new MockRegistration(mockSubject.Registrations.Behavior, mockSubject.Registrations.Prefix, Interactions)); } /// diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt index 7cfa92cd..627319b5 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt @@ -568,7 +568,7 @@ namespace Mockolate.Monitor } public sealed class MockMonitor : Mockolate.Monitor.MockMonitor { - public MockMonitor(Mockolate.Mock mock) { } + public MockMonitor(T mock) { } public Mockolate.Verify.IMockVerify Verify { get; } } } diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt index 893e0cab..1e5e8bf8 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt @@ -567,7 +567,7 @@ namespace Mockolate.Monitor } public sealed class MockMonitor : Mockolate.Monitor.MockMonitor { - public MockMonitor(Mockolate.Mock mock) { } + public MockMonitor(T mock) { } public Mockolate.Verify.IMockVerify Verify { get; } } } diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt index 4db24ded..b9a169bb 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt @@ -534,7 +534,7 @@ namespace Mockolate.Monitor } public sealed class MockMonitor : Mockolate.Monitor.MockMonitor { - public MockMonitor(Mockolate.Mock mock) { } + public MockMonitor(T mock) { } public Mockolate.Verify.IMockVerify Verify { get; } } } diff --git a/Tests/Mockolate.Tests/Monitor/MockMonitorTests.cs b/Tests/Mockolate.Tests/Monitor/MockMonitorTests.cs index 9dcd78c0..85ba828f 100644 --- a/Tests/Mockolate.Tests/Monitor/MockMonitorTests.cs +++ b/Tests/Mockolate.Tests/Monitor/MockMonitorTests.cs @@ -1,3 +1,4 @@ +using Mockolate.Exceptions; using Mockolate.Monitor; using Mockolate.Tests.TestHelpers; @@ -9,8 +10,7 @@ public sealed class MockMonitorTests public async Task ClearAllInteractions_WhenMonitorIsRunning_ShouldClearInternalCollection() { IMyService sut = Mock.Create(); - Mock mock = ((IMockSubject)sut).Mock; - MockMonitor monitor = new(mock); + MockMonitor monitor = new(sut); sut.IsValid(1); using IDisposable disposable = monitor.Run(); @@ -20,12 +20,25 @@ public async Task ClearAllInteractions_WhenMonitorIsRunning_ShouldClearInternalC await That(monitor.Verify.Invoked.IsValid(It.Is(1))).Never(); } + [Fact] + public async Task Constructor_WithoutMock_ShouldThrowMockException() + { + MyServiceBase sut = new(); + + void Act() + { + _ = new MockMonitor(sut); + } + + await That(Act).Throws() + .WithMessage("The subject is no mock."); + } + [Fact] public async Task DisposeTwice_ShouldNotIncludeMoreInvocations() { IMyService sut = Mock.Create(); - Mock mock = ((IMockSubject)sut).Mock; - MockMonitor monitor = new(mock); + MockMonitor monitor = new(sut); sut.IsValid(1); sut.IsValid(2); @@ -53,8 +66,7 @@ public async Task DisposeTwice_ShouldNotIncludeMoreInvocations() public async Task MultipleRun_ShouldMonitorInvocationsDuringTheRun() { IMyService sut = Mock.Create(); - Mock mock = ((IMockSubject)sut).Mock; - MockMonitor monitor = new(mock); + MockMonitor monitor = new(sut); sut.IsValid(1); sut.IsValid(2); @@ -91,8 +103,7 @@ public async Task MultipleRun_ShouldMonitorInvocationsDuringTheRun() public async Task NestedRun_ShouldThrowInvalidOperationException() { IMyService sut = Mock.Create(); - Mock mock = ((IMockSubject)sut).Mock; - MockMonitor monitor = new(mock); + MockMonitor monitor = new(sut); void Act() { @@ -113,8 +124,7 @@ await That(Act).Throws() public async Task Run_ShouldIncludeAllInvocations() { IMyService sut = Mock.Create(); - Mock mock = ((IMockSubject)sut).Mock; - MockMonitor monitor = new(mock); + MockMonitor monitor = new(sut); using (monitor.Run()) { @@ -134,8 +144,7 @@ public async Task Run_ShouldIncludeAllInvocations() public async Task Run_ShouldMonitorInvocationsDuringTheRun() { IMyService sut = Mock.Create(); - Mock mock = ((IMockSubject)sut).Mock; - MockMonitor monitor = new(mock); + MockMonitor monitor = new(sut); sut.IsValid(1); sut.IsValid(2); @@ -163,8 +172,7 @@ public async Task Run_ShouldMonitorInvocationsDuringTheRun() public async Task Verify_WhileRunning_ShouldBeUpToDate() { IMyService sut = Mock.Create(); - Mock mock = ((IMockSubject)sut).Mock; - MockMonitor monitor = new(mock); + MockMonitor monitor = new(sut); using (monitor.Run()) {