From 4963bf585c0647a83d1c8f9db75831fb493b3b96 Mon Sep 17 00:00:00 2001 From: chunty <6099589+chunty@users.noreply.github.com> Date: Thu, 14 May 2026 15:02:26 +0100 Subject: [PATCH] Revert "Add Moq and NSubstitute setup extension methods to TaskTurnstile.Testing" This reverts commit c43d9cc4822cd73d31f891a71029f8261067c79a. --- Directory.Packages.props | 1 - .../TaskStateManagerMoqExtensions.cs | 165 -------------- .../TaskStateManagerNSubstituteExtensions.cs | 157 ------------- .../TaskTurnstile.Testing.csproj | 10 +- .../TaskTurnstile.Tests.csproj | 1 - .../TaskStateManagerMoqExtensionsTests.cs | 210 ------------------ ...kStateManagerNSubstituteExtensionsTests.cs | 210 ------------------ wiki/Testing.md | 77 +------ 8 files changed, 9 insertions(+), 822 deletions(-) delete mode 100644 TaskTurnstile.Testing/TaskStateManagerMoqExtensions.cs delete mode 100644 TaskTurnstile.Testing/TaskStateManagerNSubstituteExtensions.cs delete mode 100644 TaskTurnstile.Tests/Testing/TaskStateManagerMoqExtensionsTests.cs delete mode 100644 TaskTurnstile.Tests/Testing/TaskStateManagerNSubstituteExtensionsTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 82057a3..e04efde 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,6 @@ - diff --git a/TaskTurnstile.Testing/TaskStateManagerMoqExtensions.cs b/TaskTurnstile.Testing/TaskStateManagerMoqExtensions.cs deleted file mode 100644 index 03ed4f9..0000000 --- a/TaskTurnstile.Testing/TaskStateManagerMoqExtensions.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Moq; -using Moq.Language.Flow; - -namespace TaskTurnstile.Testing; - -/// -/// Moq setup extensions for that eliminate boilerplate -/// on the methods that accept a delegate. -/// -/// -/// -/// The methods that take a Func<CancellationToken, Task> require verbose -/// Returns<string, Func<...>, TimeSpan?, CancellationToken> calls. -/// These extensions collapse that to a single line: -/// -/// -/// // Before -/// Mocker.GetMock<ITaskStateManager>() -/// .Setup(m => m.TryRunAsync( -/// It.IsAny<string>(), -/// It.IsAny<Func<CancellationToken, Task>>(), -/// It.IsAny<TimeSpan?>(), -/// It.IsAny<CancellationToken>())) -/// .Returns<string, Func<CancellationToken, Task>, TimeSpan?, CancellationToken>( -/// async (_, work, _, ct) => { await work(ct); return true; }); -/// -/// // After -/// Mocker.GetMock<ITaskStateManager>().SetupTryRunAsync(returns: true); -/// -/// -/// Verify calls are unchanged — they already work naturally. -/// -/// -public static class TaskStateManagerMoqExtensions -{ - /// - /// Sets up . - /// When is true, the work delegate is invoked and the call returns true. - /// When is false, work is skipped and the call returns false. - /// - /// The to configure. - /// - /// Whether the mock should run the work and return true, or skip and return false. - /// - /// - /// The task name to match. When null (default), matches any task name. - /// - public static IReturnsResult SetupTryRunAsync( - this Mock mock, - bool returns, - string? taskName = null) - { - var setup = taskName is null - ? mock.Setup(m => m.TryRunAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny())) - : mock.Setup(m => m.TryRunAsync( - taskName, - It.IsAny>(), - It.IsAny(), - It.IsAny())); - - if (returns) - { - return setup.Returns, TimeSpan?, CancellationToken>( - async (_, work, _, ct) => - { - await work(ct); - return true; - }); - } - - return setup.ReturnsAsync(false); - } - - /// - /// Sets up - /// to run the work delegate and return wrapping . - /// - /// The to configure. - /// The value to wrap in . - /// - /// The task name to match. When null (default), matches any task name. - /// - public static IReturnsResult SetupTryRunAsync( - this Mock mock, - T value, - string? taskName = null) - { - var setup = taskName is null - ? mock.Setup(m => m.TryRunAsync( - It.IsAny(), - It.IsAny>>(), - It.IsAny(), - It.IsAny())) - : mock.Setup(m => m.TryRunAsync( - taskName, - It.IsAny>>(), - It.IsAny(), - It.IsAny())); - - return setup.Returns>, TimeSpan?, CancellationToken>( - async (_, work, _, ct) => - { - await work(ct); - return TryRunResult.Ran(value); - }); - } - - /// - /// Sets up - /// to skip work and return . - /// - /// The to configure. - /// - /// The task name to match. When null (default), matches any task name. - /// - public static IReturnsResult SetupTryRunAsyncToSkip( - this Mock mock, - string? taskName = null) - { - var setup = taskName is null - ? mock.Setup(m => m.TryRunAsync( - It.IsAny(), - It.IsAny>>(), - It.IsAny(), - It.IsAny())) - : mock.Setup(m => m.TryRunAsync( - taskName, - It.IsAny>>(), - It.IsAny(), - It.IsAny())); - - return setup.ReturnsAsync(TryRunResult.Skipped); - } - - /// - /// Sets up to invoke the work delegate. - /// - /// The to configure. - /// - /// The task name to match. When null (default), matches any task name. - /// - public static IReturnsResult SetupRunAsync( - this Mock mock, - string? taskName = null) - { - var setup = taskName is null - ? mock.Setup(m => m.RunAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny())) - : mock.Setup(m => m.RunAsync( - taskName, - It.IsAny>(), - It.IsAny(), - It.IsAny())); - - return setup.Returns, TimeSpan?, CancellationToken>( - async (_, work, _, ct) => await work(ct)); - } -} diff --git a/TaskTurnstile.Testing/TaskStateManagerNSubstituteExtensions.cs b/TaskTurnstile.Testing/TaskStateManagerNSubstituteExtensions.cs deleted file mode 100644 index 7171c84..0000000 --- a/TaskTurnstile.Testing/TaskStateManagerNSubstituteExtensions.cs +++ /dev/null @@ -1,157 +0,0 @@ -using NSubstitute; -using NSubstitute.Core; - -namespace TaskTurnstile.Testing; - -/// -/// NSubstitute setup extensions for that eliminate boilerplate -/// on the methods that accept a delegate. -/// -/// -/// -/// // Before -/// manager.TryRunAsync( -/// Arg.Any<string>(), -/// Arg.Any<Func<CancellationToken, Task>>(), -/// Arg.Any<TimeSpan?>(), -/// Arg.Any<CancellationToken>()) -/// .Returns(async ci => -/// { -/// await ci.Arg<Func<CancellationToken, Task>>()(CancellationToken.None); -/// return true; -/// }); -/// -/// // After -/// manager.SetupTryRunAsync(returns: true); -/// -/// -public static class TaskStateManagerNSubstituteExtensions -{ - /// - /// Sets up . - /// When is true, the work delegate is invoked and the call returns true. - /// When is false, work is skipped and the call returns false. - /// - /// The NSubstitute substitute to configure. - /// - /// Whether the substitute should run the work and return true, or skip and return false. - /// - /// - /// The task name to match. When null (default), matches any task name. - /// - public static ConfiguredCall SetupTryRunAsync( - this ITaskStateManager manager, - bool returns, - string? taskName = null) - { - var configuredCall = taskName is null - ? manager.TryRunAsync( - Arg.Any(), - Arg.Any>(), - Arg.Any(), - Arg.Any()) - : manager.TryRunAsync( - taskName, - Arg.Any>(), - Arg.Any(), - Arg.Any()); - - if (returns) - { - return configuredCall.Returns(async (CallInfo ci) => - { - await ci.Arg>()(ci.Arg()); - return true; - }); - } - - return configuredCall.Returns(false); - } - - /// - /// Sets up - /// to run the work delegate and return wrapping . - /// - /// The NSubstitute substitute to configure. - /// The value to wrap in . - /// - /// The task name to match. When null (default), matches any task name. - /// - public static ConfiguredCall SetupTryRunAsync( - this ITaskStateManager manager, - T value, - string? taskName = null) - { - var configuredCall = taskName is null - ? manager.TryRunAsync( - Arg.Any(), - Arg.Any>>(), - Arg.Any(), - Arg.Any()) - : manager.TryRunAsync( - taskName, - Arg.Any>>(), - Arg.Any(), - Arg.Any()); - - return configuredCall.Returns(async (CallInfo ci) => - { - await ci.Arg>>()(ci.Arg()); - return TryRunResult.Ran(value); - }); - } - - /// - /// Sets up - /// to skip work and return . - /// - /// The NSubstitute substitute to configure. - /// - /// The task name to match. When null (default), matches any task name. - /// - public static ConfiguredCall SetupTryRunAsyncToSkip( - this ITaskStateManager manager, - string? taskName = null) - { - var configuredCall = taskName is null - ? manager.TryRunAsync( - Arg.Any(), - Arg.Any>>(), - Arg.Any(), - Arg.Any()) - : manager.TryRunAsync( - taskName, - Arg.Any>>(), - Arg.Any(), - Arg.Any()); - - return configuredCall.Returns(TryRunResult.Skipped); - } - - /// - /// Sets up to invoke the work delegate. - /// - /// The NSubstitute substitute to configure. - /// - /// The task name to match. When null (default), matches any task name. - /// - public static ConfiguredCall SetupRunAsync( - this ITaskStateManager manager, - string? taskName = null) - { - var configuredCall = taskName is null - ? manager.RunAsync( - Arg.Any(), - Arg.Any>(), - Arg.Any(), - Arg.Any()) - : manager.RunAsync( - taskName, - Arg.Any>(), - Arg.Any(), - Arg.Any()); - - return configuredCall.Returns(async (CallInfo ci) => - await ci.Arg>()(ci.Arg())); - } -} diff --git a/TaskTurnstile.Testing/TaskTurnstile.Testing.csproj b/TaskTurnstile.Testing/TaskTurnstile.Testing.csproj index 2aa5c7c..cb30da9 100644 --- a/TaskTurnstile.Testing/TaskTurnstile.Testing.csproj +++ b/TaskTurnstile.Testing/TaskTurnstile.Testing.csproj @@ -3,11 +3,11 @@ TaskTurnstile.Testing - Test helpers for TaskTurnstile. Provides FakeTaskStateManager (framework-agnostic in-memory - test double) and setup extension methods for Moq and NSubstitute that eliminate boilerplate - when mocking ITaskStateManager methods that accept a Func delegate. + Framework-agnostic test double for TaskTurnstile. Provides FakeTaskStateManager, + an in-memory implementation of ITaskStateManager that executes work inline — no + mocking framework required. - Concurrency, Background Tasks, Named Lock, Testing, Fake, Test Double, Moq, NSubstitute + Concurrency, Background Tasks, Named Lock, Testing, Fake, Test Double NugetREADME.md @@ -17,8 +17,6 @@ - - diff --git a/TaskTurnstile.Tests/TaskTurnstile.Tests.csproj b/TaskTurnstile.Tests/TaskTurnstile.Tests.csproj index cbb1da6..8695a09 100644 --- a/TaskTurnstile.Tests/TaskTurnstile.Tests.csproj +++ b/TaskTurnstile.Tests/TaskTurnstile.Tests.csproj @@ -10,7 +10,6 @@ - diff --git a/TaskTurnstile.Tests/Testing/TaskStateManagerMoqExtensionsTests.cs b/TaskTurnstile.Tests/Testing/TaskStateManagerMoqExtensionsTests.cs deleted file mode 100644 index 956f934..0000000 --- a/TaskTurnstile.Tests/Testing/TaskStateManagerMoqExtensionsTests.cs +++ /dev/null @@ -1,210 +0,0 @@ -using Moq; -using TaskTurnstile.Testing; - -namespace TaskTurnstile.Tests.Testing; - -public class TaskStateManagerMoqExtensionsTests -{ - // ── SetupTryRunAsync (bool) ─────────────────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsync_ReturnsTrue_RunsWork() - { - var mock = new Mock(); - mock.SetupTryRunAsync(returns: true); - - var workRan = false; - var result = await mock.Object.TryRunAsync("job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(result); - Assert.True(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_ReturnsFalse_SkipsWork() - { - var mock = new Mock(); - mock.SetupTryRunAsync(returns: false); - - var workRan = false; - var result = await mock.Object.TryRunAsync("job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.False(result); - Assert.False(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_ReturnsTrue_PassesCancellationTokenToWork() - { - var mock = new Mock(); - mock.SetupTryRunAsync(returns: true); - - using var cts = new CancellationTokenSource(); - CancellationToken captured = default; - - await mock.Object.TryRunAsync("job", - ct => { captured = ct; return Task.CompletedTask; }, - cancellationToken: cts.Token); - - Assert.Equal(cts.Token, captured); - } - - [Fact] - public async Task SetupTryRunAsync_WithTaskName_OnlyMatchesSpecifiedName() - { - var mock = new Mock(); - mock.SetupTryRunAsync(returns: true, taskName: "import-job"); - - var workRan = false; - await mock.Object.TryRunAsync("import-job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_WithTaskName_DoesNotMatchOtherName() - { - var mock = new Mock(); - mock.SetupTryRunAsync(returns: true, taskName: "import-job"); - - // No setup for "other-job" — returns default (false) - var result = await mock.Object.TryRunAsync("other-job", - _ => Task.CompletedTask, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.False(result); - } - - // ── SetupTryRunAsync ─────────────────────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsync_Generic_RunsWorkAndReturnsRanWithValue() - { - var mock = new Mock(); - mock.SetupTryRunAsync(value: 42); - - var workRan = false; - var result = await mock.Object.TryRunAsync("job", - _ => { workRan = true; return Task.FromResult(0); }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(result.Started); - Assert.Equal(42, result.Value); - Assert.True(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_Generic_WithTaskName_OnlyMatchesSpecifiedName() - { - var mock = new Mock(); - mock.SetupTryRunAsync(value: 99, taskName: "my-job"); - - var result = await mock.Object.TryRunAsync("my-job", - _ => Task.FromResult(0), - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(result.Started); - Assert.Equal(99, result.Value); - } - - // ── SetupTryRunAsyncToSkip ───────────────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsyncToSkip_ReturnsSkipped_WorkNotRun() - { - var mock = new Mock(); - mock.SetupTryRunAsyncToSkip(); - - var workRan = false; - var result = await mock.Object.TryRunAsync("job", - _ => { workRan = true; return Task.FromResult(0); }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal(TryRunResult.Skipped, result); - Assert.False(workRan); - } - - [Fact] - public async Task SetupTryRunAsyncToSkip_WithTaskName_OnlyMatchesSpecifiedName() - { - var mock = new Mock(); - mock.SetupTryRunAsyncToSkip(taskName: "skip-job"); - - var result = await mock.Object.TryRunAsync("skip-job", - _ => Task.FromResult(0), - cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal(TryRunResult.Skipped, result); - } - - // ── SetupRunAsync ───────────────────────────────────────────────────────── - - [Fact] - public async Task SetupRunAsync_RunsWork() - { - var mock = new Mock(); - mock.SetupRunAsync(); - - var workRan = false; - await mock.Object.RunAsync("job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(workRan); - } - - [Fact] - public async Task SetupRunAsync_PassesCancellationTokenToWork() - { - var mock = new Mock(); - mock.SetupRunAsync(); - - using var cts = new CancellationTokenSource(); - CancellationToken captured = default; - - await mock.Object.RunAsync("job", - ct => { captured = ct; return Task.CompletedTask; }, - cancellationToken: cts.Token); - - Assert.Equal(cts.Token, captured); - } - - [Fact] - public async Task SetupRunAsync_WithTaskName_OnlyMatchesSpecifiedName() - { - var mock = new Mock(); - mock.SetupRunAsync(taskName: "export-job"); - - var workRan = false; - await mock.Object.RunAsync("export-job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(workRan); - } - - // ── Verify still works after setup ─────────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsync_VerifyByTaskName_Works() - { - var mock = new Mock(); - mock.SetupTryRunAsync(returns: true); - - await mock.Object.TryRunAsync("import-job", - _ => Task.CompletedTask, - cancellationToken: TestContext.Current.CancellationToken); - - mock.Verify(m => m.TryRunAsync( - "import-job", - It.IsAny>(), - It.IsAny(), - It.IsAny()), Times.Once); - } -} diff --git a/TaskTurnstile.Tests/Testing/TaskStateManagerNSubstituteExtensionsTests.cs b/TaskTurnstile.Tests/Testing/TaskStateManagerNSubstituteExtensionsTests.cs deleted file mode 100644 index a19e7cd..0000000 --- a/TaskTurnstile.Tests/Testing/TaskStateManagerNSubstituteExtensionsTests.cs +++ /dev/null @@ -1,210 +0,0 @@ -using NSubstitute; -using TaskTurnstile.Testing; - -namespace TaskTurnstile.Tests.Testing; - -public class TaskStateManagerNSubstituteExtensionsTests -{ - // ── SetupTryRunAsync (bool) ─────────────────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsync_ReturnsTrue_RunsWork() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(returns: true); - - var workRan = false; - var result = await sub.TryRunAsync("job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(result); - Assert.True(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_ReturnsFalse_SkipsWork() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(returns: false); - - var workRan = false; - var result = await sub.TryRunAsync("job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.False(result); - Assert.False(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_ReturnsTrue_PassesCancellationTokenToWork() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(returns: true); - - using var cts = new CancellationTokenSource(); - CancellationToken captured = default; - - await sub.TryRunAsync("job", - ct => { captured = ct; return Task.CompletedTask; }, - cancellationToken: cts.Token); - - Assert.Equal(cts.Token, captured); - } - - [Fact] - public async Task SetupTryRunAsync_WithTaskName_OnlyMatchesSpecifiedName() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(returns: true, taskName: "import-job"); - - var workRan = false; - await sub.TryRunAsync("import-job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_WithTaskName_DoesNotMatchOtherName() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(returns: true, taskName: "import-job"); - - // No setup for "other-job" — returns default (false) - var result = await sub.TryRunAsync("other-job", - _ => Task.CompletedTask, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.False(result); - } - - // ── SetupTryRunAsync ─────────────────────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsync_Generic_RunsWorkAndReturnsRanWithValue() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(value: 42); - - var workRan = false; - var result = await sub.TryRunAsync("job", - _ => { workRan = true; return Task.FromResult(0); }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(result.Started); - Assert.Equal(42, result.Value); - Assert.True(workRan); - } - - [Fact] - public async Task SetupTryRunAsync_Generic_WithTaskName_OnlyMatchesSpecifiedName() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(value: 99, taskName: "my-job"); - - var result = await sub.TryRunAsync("my-job", - _ => Task.FromResult(0), - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(result.Started); - Assert.Equal(99, result.Value); - } - - // ── SetupTryRunAsyncToSkip ───────────────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsyncToSkip_ReturnsSkipped_WorkNotRun() - { - var sub = Substitute.For(); - sub.SetupTryRunAsyncToSkip(); - - var workRan = false; - var result = await sub.TryRunAsync("job", - _ => { workRan = true; return Task.FromResult(0); }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal(TryRunResult.Skipped, result); - Assert.False(workRan); - } - - [Fact] - public async Task SetupTryRunAsyncToSkip_WithTaskName_OnlyMatchesSpecifiedName() - { - var sub = Substitute.For(); - sub.SetupTryRunAsyncToSkip(taskName: "skip-job"); - - var result = await sub.TryRunAsync("skip-job", - _ => Task.FromResult(0), - cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal(TryRunResult.Skipped, result); - } - - // ── SetupRunAsync ───────────────────────────────────────────────────────── - - [Fact] - public async Task SetupRunAsync_RunsWork() - { - var sub = Substitute.For(); - sub.SetupRunAsync(); - - var workRan = false; - await sub.RunAsync("job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(workRan); - } - - [Fact] - public async Task SetupRunAsync_PassesCancellationTokenToWork() - { - var sub = Substitute.For(); - sub.SetupRunAsync(); - - using var cts = new CancellationTokenSource(); - CancellationToken captured = default; - - await sub.RunAsync("job", - ct => { captured = ct; return Task.CompletedTask; }, - cancellationToken: cts.Token); - - Assert.Equal(cts.Token, captured); - } - - [Fact] - public async Task SetupRunAsync_WithTaskName_OnlyMatchesSpecifiedName() - { - var sub = Substitute.For(); - sub.SetupRunAsync(taskName: "export-job"); - - var workRan = false; - await sub.RunAsync("export-job", - _ => { workRan = true; return Task.CompletedTask; }, - cancellationToken: TestContext.Current.CancellationToken); - - Assert.True(workRan); - } - - // ── Received() still works after setup ─────────────────────────────────── - - [Fact] - public async Task SetupTryRunAsync_ReceivedByTaskName_Works() - { - var sub = Substitute.For(); - sub.SetupTryRunAsync(returns: true); - - await sub.TryRunAsync("import-job", - _ => Task.CompletedTask, - cancellationToken: TestContext.Current.CancellationToken); - - await sub.Received(1).TryRunAsync( - "import-job", - Arg.Any>(), - Arg.Any(), - Arg.Any()); - } -} diff --git a/wiki/Testing.md b/wiki/Testing.md index 5a5449c..d58c7c7 100644 --- a/wiki/Testing.md +++ b/wiki/Testing.md @@ -1,6 +1,6 @@ # Testing -## Setup extension methods (recommended for Moq / NSubstitute) +## FakeTaskStateManager (recommended) Add [`TaskTurnstile.Testing`](https://www.nuget.org/packages/TaskTurnstile.Testing) to your test project: @@ -8,76 +8,9 @@ Add [`TaskTurnstile.Testing`](https://www.nuget.org/packages/TaskTurnstile.Testi dotnet add package TaskTurnstile.Testing ``` -The methods that take a `Func` delegate (`TryRunAsync`, `RunAsync`) require verbose generic type arguments when set up with Moq or NSubstitute. The Testing package provides one-liner extension methods that eliminate that boilerplate while leaving `Verify` / `Received` completely unchanged. +`FakeTaskStateManager` is a framework-agnostic in-memory implementation of `ITaskStateManager`. It executes work inline with no distributed state, no polling, and no boilerplate. -### Moq / AutoMocker - -```csharp -using TaskTurnstile.Testing; - -// TryRunAsync — runs work and returns true -Mocker.GetMock().SetupTryRunAsync(returns: true); - -// TryRunAsync — skips and returns false -Mocker.GetMock().SetupTryRunAsync(returns: false); - -// RunAsync — runs work -Mocker.GetMock().SetupRunAsync(); - -// Generic TryRunAsync — runs work, returns Ran(value) -Mocker.GetMock().SetupTryRunAsync(value: 42); - -// Generic TryRunAsync — skips -Mocker.GetMock().SetupTryRunAsyncToSkip(); -``` - -### NSubstitute - -```csharp -using TaskTurnstile.Testing; - -var manager = Substitute.For(); - -manager.SetupTryRunAsync(returns: true); -manager.SetupTryRunAsync(returns: false); -manager.SetupRunAsync(); -manager.SetupTryRunAsync(value: 42); -manager.SetupTryRunAsyncToSkip(); -``` - -### Matching a specific task name - -Pass `taskName` to make the setup match only that name. Any other name falls through to the Moq / NSubstitute default. This is useful when the thing you're testing is *which* name gets passed: - -```csharp -// Moq -Mocker.GetMock().SetupTryRunAsync(returns: true, taskName: "import-job"); - -// Verify the correct name was used -Mocker.GetMock() - .Verify(m => m.TryRunAsync( - "import-job", - It.IsAny>(), - It.IsAny(), - It.IsAny()), Times.Once); -``` - -```csharp -// NSubstitute -manager.SetupTryRunAsync(returns: true, taskName: "import-job"); - -await manager.Received(1).TryRunAsync( - "import-job", - Arg.Any>(), - Arg.Any(), - Arg.Any()); -``` - ---- - -## FakeTaskStateManager - -`FakeTaskStateManager` is a framework-agnostic in-memory implementation of `ITaskStateManager`. It executes work inline with no distributed state, no polling, and no boilerplate. Use it when you don't need to verify which task name was passed — it's the simplest option for "does the handler actually do the work" tests. +### Works with any mocking framework ```csharp // AutoMocker (Moq) — inject the fake instead of setting up a mock @@ -126,7 +59,7 @@ var fake = new FakeTaskStateManager { WaitTimeout = TimeSpan.FromSeconds(10) }; ## Manual mocking (without TaskTurnstile.Testing) -If you prefer to wire up your mocking framework directly without the helper extensions, the key is capturing and invoking the `work` delegate inside the `Returns` callback. +If you prefer to wire up your mocking framework directly, the key is capturing and invoking the `work` delegate inside the `Returns` callback. ### Moq / AutoMocker — TryRunAsync runs work, returns `true` @@ -163,7 +96,7 @@ manager.TryRunAsync( Arg.Any()) .Returns(async ci => { - await ci.Arg>()(ci.Arg()); + await ci.Arg>()(CancellationToken.None); return true; }); ```