Skip to content

Commit 6dafe3f

Browse files
committed
TUnit
1 parent 87bdb06 commit 6dafe3f

117 files changed

Lines changed: 2309 additions & 1870 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/copilot-instructions.md‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,23 @@ dotnet test
3838

3939
## Testing
4040

41-
* We use xUnit for tests with FluentAssertions for assertions.
41+
* We use TUnit on Microsoft.Testing.Platform with Shouldly for assertions.
4242
* Do not emit "Arrange", "Act" or "Assert" comments.
4343
* Use the naming pattern: `Method_Scenario_ExpectedResult()`.
4444
* Copy existing style in nearby test files for method names and capitalization.
4545

4646
```csharp
47-
[Fact]
47+
[Test]
4848
public void Succeed_ShouldCreateSuccessfulResult()
4949
{
5050
var result = Result.Succeed();
5151

5252
result.IsSuccess
53-
.Should()
54-
.BeTrue();
53+
.ShouldBeTrue();
5554
result.IsFailed
56-
.Should()
57-
.BeFalse();
55+
.ShouldBeFalse();
5856
result.Problem
59-
.Should()
60-
.BeNull();
57+
.ShouldBeNull();
6158
}
6259
```
6360

@@ -143,4 +140,4 @@ if (result.IsFailed)
143140
**Performance:**
144141
* `Result` and `Result<T>` are structs - avoid boxing
145142
* Use railway-oriented programming to avoid intermediate variables
146-
* Cache common Problem instances for frequent errors
143+
* Cache common Problem instances for frequent errors

‎.github/workflows/ci.yml‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ jobs:
3030
run: dotnet build --configuration Release --no-restore
3131

3232
- name: Test
33-
run: dotnet test --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage"
33+
run: >-
34+
dotnet test --solution ManagedCode.Communication.slnx
35+
--configuration Release
36+
--no-build
37+
--coverage
38+
--coverage-output coverage.cobertura.xml
39+
--coverage-output-format cobertura
40+
--output Normal
3441
3542
- name: Upload coverage reports to Codecov
3643
uses: codecov/codecov-action@v7

‎.github/workflows/release.yml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ jobs:
3939
run: dotnet build --configuration Release --no-restore
4040

4141
- name: Test
42-
run: dotnet test --configuration Release --no-build --verbosity normal
42+
run: >-
43+
dotnet test --solution ManagedCode.Communication.slnx
44+
--configuration Release
45+
--no-build
46+
--output Normal
4347
4448
- name: Pack NuGet packages
4549
run: dotnet pack --configuration Release --no-build --output ./artifacts

‎AGENTS.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ If I tell you to remember something, you do the same, update
1010

1111
## Rules to follow
1212
always check all test are passed.
13+
- Use TUnit with Microsoft.Testing.Platform for all .NET tests; do not add or retain xUnit packages, attributes, fixtures, configuration, or documentation.
1314
- Prefer static interface members for result/command factories to centralize shared overloads and avoid duplication across result-like types.
1415
- Use `DateTime.UtcNow` (never `DateTimeOffset`) for all timestamps; we assume every stored time is in UTC.
1516
- For `MergeAll`/`CombineAll` scenarios with mixed failures, keep aggregated behavior and preserve original errors in `Problem.Extensions` (do not flatten everything into validation-only output).
@@ -41,15 +42,15 @@ The solution `ManagedCode.Communication.slnx` ties together the core library (`M
4142
## Build, Test, and Development Commands
4243
- `dotnet restore ManagedCode.Communication.slnx` – restore all project dependencies.
4344
- `dotnet build -c Release ManagedCode.Communication.slnx` – compile every project with warnings treated as errors.
44-
- `dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj` – run the xUnit suite; produces `*.trx` logs under `ManagedCode.Communication.Tests`.
45-
- `dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj /p:CollectCoverage=true /p:CoverletOutputFormat=lcov` – refresh `coverage.info` via coverlet.
45+
- `dotnet test --project ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj` – run the complete TUnit suite on Microsoft.Testing.Platform.
46+
- `dotnet test --project ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --coverage --coverage-output-format cobertura` – collect coverage through Microsoft.Testing.Platform.
4647
- `dotnet run -c Release --project ManagedCode.Communication.Benchmark` – execute benchmark scenarios before performance-sensitive changes.
4748

4849
## Coding Style & Naming Conventions
4950
Formatting is driven by the root `.editorconfig`: spaces only, 4-space indent for C#, CRLF endings for code, braces on new lines, and explicit types except when the type is obvious. The repo builds with C# 13, nullable reference types enabled, and analyzers elevated to errors—leave no compiler warnings behind. Stick to domain-centric names (e.g., `ResultExtensionsTests`) and prefer PascalCase for members and const fields per the configured naming rules.
5051

5152
## Testing Guidelines
52-
All automated tests use xUnit with Shouldly and Microsoft test hosts; follow the existing spec style (`MethodUnderTest_WithScenario_ShouldOutcome`). New fixtures belong in the matching feature folder and should assert both success and failure branches for Result types. Maintain the default coverage settings supplied by `coverlet.collector`; update snapshots or helper helpers under `TestHelpers` (including shared Shouldly extensions) when shared setup changes.
53+
All automated tests use TUnit with Shouldly on Microsoft.Testing.Platform; follow the existing spec style (`MethodUnderTest_WithScenario_ShouldOutcome`). New fixtures belong in the matching feature folder and should assert both success and failure branches for Result types. Use TUnit data sources and lifecycle hooks, collect coverage through `Microsoft.Testing.Extensions.CodeCoverage`, and update shared helpers under `TestHelpers` when shared setup changes.
5354

5455
## Commit & Pull Request Guidelines
5556
Commits in this repository stay short, imperative, and often reference the related issue or PR number (e.g., `Add FailBadRequest methods (#30)`). Mirror that tone, limit each commit to a coherent change, and include updates to docs or benchmarks when behavior shifts. Pull requests should summarize intent, list breaking changes, attach relevant `dotnet test` outputs or coverage deltas, and link tracked issues. Screenshots or sample payloads are welcome for HTTP-facing work.

‎ManagedCode.Communication.Tests/AspNetCore/Extensions/CommandCleanupBackgroundServiceIntegrationTests.cs‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Collections.Generic;
21
using System;
2+
using System.Collections.Generic;
33
using System.Linq;
44
using System.Threading.Tasks;
55
using ManagedCode.Communication;
@@ -10,13 +10,12 @@
1010
using Microsoft.AspNetCore.TestHost;
1111
using Microsoft.Extensions.DependencyInjection;
1212
using Shouldly;
13-
using Xunit;
1413

1514
namespace ManagedCode.Communication.Tests.AspNetCore.Extensions;
1615

1716
public class CommandCleanupBackgroundServiceIntegrationTests
1817
{
19-
[Fact]
18+
[Test]
2019
public async Task CommandCleanupBackgroundService_PerformsCleanupAndHealthLogging()
2120
{
2221
var store = new TrackingCleanupStore();
@@ -38,7 +37,7 @@ public async Task CommandCleanupBackgroundService_PerformsCleanupAndHealthLoggin
3837
await app.StopAsync();
3938
}
4039

41-
[Fact]
40+
[Test]
4241
public async Task CommandCleanupBackgroundService_ContinuesAfterCleanupFailure()
4342
{
4443
var store = new TrackingCleanupStore(throwOnFirstCompletedCleanup: true);

‎ManagedCode.Communication.Tests/AspNetCore/Extensions/CommandCleanupExtensionsTests.cs‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
using ManagedCode.Communication.AspNetCore.Extensions;
66
using ManagedCode.Communication.Commands;
77
using Shouldly;
8-
using Xunit;
98

109
namespace ManagedCode.Communication.Tests.AspNetCore.Extensions;
1110

1211
public class CommandCleanupExtensionsTests
1312
{
14-
[Fact]
13+
[Test]
1514
public async Task AutoCleanupAsync_UsesDefaultAgesAndAggregatesResults()
1615
{
1716
var store = new TrackingCommandIdempotencyStore
@@ -33,7 +32,7 @@ public async Task AutoCleanupAsync_UsesDefaultAgesAndAggregatesResults()
3332
store.CleanupCalls[2].MaxAge.ShouldBe(TimeSpan.FromMinutes(30));
3433
}
3534

36-
[Fact]
35+
[Test]
3736
public async Task GetHealthMetricsAsync_ReturnsCommandCountsAndRatios()
3837
{
3938
var store = new TrackingCommandIdempotencyStore
@@ -58,7 +57,7 @@ public async Task GetHealthMetricsAsync_ReturnsCommandCountsAndRatios()
5857
metrics.FailureRate.ShouldBe(10);
5958
}
6059

61-
[Fact]
60+
[Test]
6261
public async Task GetHealthMetricsAsync_WhenNoCommands_ReturnsZeroRates()
6362
{
6463
var store = new TrackingCommandIdempotencyStore

‎ManagedCode.Communication.Tests/AspNetCore/Extensions/CommandIdempotencyExtensionsTests.cs‎

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
using ManagedCode.Communication.Commands;
77
using ManagedCode.Communication.Commands.Extensions;
88
using Shouldly;
9-
using Xunit;
109

1110
namespace ManagedCode.Communication.Tests.AspNetCore.Extensions;
1211

1312
public class CommandIdempotencyExtensionsTests
1413
{
15-
[Fact]
14+
[Test]
1615
public async Task ExecuteIdempotentAsync_ReturnsCompletedResultWithoutExecuting()
1716
{
1817
var store = new TestCommandIdempotencyStoreSimulator();
@@ -34,7 +33,7 @@ public async Task ExecuteIdempotentAsync_ReturnsCompletedResultWithoutExecuting(
3433
store.SetCommandResultCalls.ShouldBe(0);
3534
}
3635

37-
[Fact]
36+
[Test]
3837
public async Task ExecuteIdempotentAsync_ExecutesWhenNotFoundAndPersistsResult()
3938
{
4039
var store = new TestCommandIdempotencyStoreSimulator();
@@ -56,7 +55,7 @@ public async Task ExecuteIdempotentAsync_ExecutesWhenNotFoundAndPersistsResult()
5655
((string?)store.GetResult("cmd-run")).ShouldBe("executed");
5756
}
5857

59-
[Fact]
58+
[Test]
6059
public async Task ExecuteIdempotentAsync_WhenExecutionFails_MarksFailedAndRethrows()
6160
{
6261
var store = new TestCommandIdempotencyStoreSimulator();
@@ -73,7 +72,7 @@ await ManagedCode.Communication.Commands.Extensions.CommandIdempotencyExtensions
7372
store.GetStatus("cmd-fail").ShouldBe(CommandExecutionStatus.Failed);
7473
}
7574

76-
[Fact]
75+
[Test]
7776
public async Task ExecuteIdempotentAsync_WhenInProgressWaitsUntilCompleted()
7877
{
7978
var store = new TestCommandIdempotencyStoreSimulator();
@@ -93,7 +92,7 @@ public async Task ExecuteIdempotentAsync_WhenInProgressWaitsUntilCompleted()
9392
store.GetCommandResultCalls.ShouldBe(1);
9493
}
9594

96-
[Fact]
95+
[Test]
9796
public async Task ExecuteIdempotentWithRetryAsync_RetriesAfterFailureAndReturnsResult()
9897
{
9998
var store = new TestCommandIdempotencyStoreSimulator();
@@ -121,7 +120,7 @@ public async Task ExecuteIdempotentWithRetryAsync_RetriesAfterFailureAndReturnsR
121120
store.GetStatus("cmd-retry").ShouldBe(CommandExecutionStatus.Completed);
122121
}
123122

124-
[Fact]
123+
[Test]
125124
public async Task ExecuteBatchIdempotentAsync_ReturnsCachedResultsAndExecutesPending()
126125
{
127126
var store = new TestCommandIdempotencyStoreSimulator();
@@ -145,7 +144,7 @@ public async Task ExecuteBatchIdempotentAsync_ReturnsCachedResultsAndExecutesPen
145144
store.GetCommandResultCalls.ShouldBe(0);
146145
}
147146

148-
[Fact]
147+
[Test]
149148
public async Task TryGetCachedResultAsync_WhenCompleted_ReturnsValue()
150149
{
151150
var store = new TestCommandIdempotencyStoreSimulator();
@@ -160,7 +159,7 @@ public async Task TryGetCachedResultAsync_WhenCompleted_ReturnsValue()
160159
result.ShouldBe("done");
161160
}
162161

163-
[Fact]
162+
[Test]
164163
public async Task TryGetCachedResultAsync_WhenNotCompleted_ReturnsEmpty()
165164
{
166165
var store = new TestCommandIdempotencyStoreSimulator();
@@ -173,7 +172,7 @@ public async Task TryGetCachedResultAsync_WhenNotCompleted_ReturnsEmpty()
173172
result.ShouldBeNull();
174173
}
175174

176-
[Fact]
175+
[Test]
177176
public async Task ExecuteWithTimeoutAsync_ReturnsResultWhenWithinTimeout()
178177
{
179178
var store = new TestCommandIdempotencyStoreSimulator();
@@ -191,7 +190,7 @@ public async Task ExecuteWithTimeoutAsync_ReturnsResultWhenWithinTimeout()
191190
result.ShouldBe("quick");
192191
}
193192

194-
[Fact]
193+
[Test]
195194
public async Task ExecuteBatchIdempotentAsync_AllCompletedResults_DoNotExecutePendingOperations()
196195
{
197196
var store = new TestCommandIdempotencyStoreSimulator();
@@ -226,7 +225,7 @@ public async Task ExecuteBatchIdempotentAsync_AllCompletedResults_DoNotExecutePe
226225
store.SetCommandResultCalls.ShouldBe(0);
227226
}
228227

229-
[Fact]
228+
[Test]
230229
public async Task ExecuteIdempotentAsync_WhenWaitForCompletionHitsFailed_Throws()
231230
{
232231
var store = new TestCommandIdempotencyStoreSimulator();
@@ -239,7 +238,7 @@ await ManagedCode.Communication.Commands.Extensions.CommandIdempotencyExtensions
239238
() => Task.FromResult("should-not-run")));
240239
}
241240

242-
[Fact]
241+
[Test]
243242
public async Task ExecuteIdempotentAsync_WhenWaitForCompletionHitsNotFound_Throws()
244243
{
245244
var store = new TestCommandIdempotencyStoreSimulator();
@@ -252,7 +251,7 @@ await ManagedCode.Communication.Commands.Extensions.CommandIdempotencyExtensions
252251
() => Task.FromResult("should-not-run")));
253252
}
254253

255-
[Fact]
254+
[Test]
256255
public async Task ExecuteIdempotentWithRetryAsync_WhenMaxRetriesExceeded_ThrowsLastException()
257256
{
258257
var store = new TestCommandIdempotencyStoreSimulator();
@@ -274,7 +273,7 @@ public async Task ExecuteIdempotentWithRetryAsync_WhenMaxRetriesExceeded_ThrowsL
274273
exception.Message.ShouldBe("attempt-2");
275274
}
276275

277-
[Fact]
276+
[Test]
278277
public async Task ExecuteIdempotentWithRetryAsync_CancelledToken_StopsWithoutExecutingOperation()
279278
{
280279
var store = new TestCommandIdempotencyStoreSimulator();

‎ManagedCode.Communication.Tests/AspNetCore/Extensions/CommandIdempotencyServiceCollectionExtensionsTests.cs‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
using Microsoft.Extensions.DependencyInjection;
66
using Microsoft.Extensions.Hosting;
77
using Shouldly;
8-
using Xunit;
98

109
namespace ManagedCode.Communication.Tests.AspNetCore.Extensions;
1110

1211
public class CommandIdempotencyServiceCollectionExtensionsTests
1312
{
14-
[Fact]
13+
[Test]
1514
public void AddCommandIdempotency_RegistersStoreAndBackgroundCleanup()
1615
{
1716
var services = new ServiceCollection();
@@ -38,7 +37,7 @@ public void AddCommandIdempotency_RegistersStoreAndBackgroundCleanup()
3837
hosted.Count.ShouldBe(1);
3938
}
4039

41-
[Fact]
40+
[Test]
4241
public void AddCommandIdempotency_WithInstance_RegistersSameStoreInstance()
4342
{
4443
var services = new ServiceCollection();
@@ -56,7 +55,7 @@ public void AddCommandIdempotency_WithInstance_RegistersSameStoreInstance()
5655
hosted.Count.ShouldBe(1);
5756
}
5857

59-
[Fact]
58+
[Test]
6059
public void AddCommandIdempotencyStore_DoesNotRegisterBackgroundCleanup()
6160
{
6261
var services = new ServiceCollection();
@@ -72,7 +71,7 @@ public void AddCommandIdempotencyStore_DoesNotRegisterBackgroundCleanup()
7271
hosted.ShouldBeEmpty();
7372
}
7473

75-
[Fact]
74+
[Test]
7675
public void AddCommandIdempotencyWithManualCleanup_DoesNotRegisterHostedServiceAndPreservesOptions()
7776
{
7877
var services = new ServiceCollection();

0 commit comments

Comments
 (0)