@grimoire-cc/cli 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,438 @@
1
+ # TUnit Test File Template
2
+
3
+ Standard template for TUnit test files (recommended for .NET 8+ projects).
4
+
5
+ ## Basic Test Class Template
6
+
7
+ ```csharp
8
+ using Microsoft.Extensions.Logging;
9
+ using Microsoft.Extensions.Logging.Testing;
10
+ using Moq;
11
+ using TUnit.Core;
12
+ using TUnit.Assertions;
13
+ using TUnit.Assertions.Extensions;
14
+
15
+ namespace YourProject.Tests.Services;
16
+
17
+ public class YourServiceTests : IAsyncDisposable
18
+ {
19
+ private readonly FakeLogger<YourService> _fakeLogger;
20
+ private readonly Mock<IDependency> _mockDependency;
21
+ private readonly YourService _sut; // System Under Test
22
+
23
+ public YourServiceTests()
24
+ {
25
+ _fakeLogger = new FakeLogger<YourService>();
26
+ _mockDependency = new Mock<IDependency>();
27
+ _sut = new YourService(_fakeLogger, _mockDependency.Object);
28
+ }
29
+
30
+ public ValueTask DisposeAsync()
31
+ {
32
+ // Cleanup resources if needed
33
+ return ValueTask.CompletedTask;
34
+ }
35
+
36
+ #region MethodName Tests
37
+
38
+ [Test]
39
+ public async Task MethodName_WhenConditionIsTrue_ShouldReturnExpectedResult()
40
+ {
41
+ // Arrange
42
+ var input = CreateTestInput();
43
+ _mockDependency
44
+ .Setup(d => d.GetDataAsync(It.IsAny<Guid>()))
45
+ .ReturnsAsync(expectedData);
46
+
47
+ // Act
48
+ var result = await _sut.MethodNameAsync(input);
49
+
50
+ // Assert
51
+ await Assert.That(result).IsNotNull();
52
+ await Assert.That(result.Property).IsEqualTo(expected);
53
+ }
54
+
55
+ [Test]
56
+ public async Task MethodName_WhenDependencyFails_ShouldThrowException()
57
+ {
58
+ // Arrange
59
+ _mockDependency
60
+ .Setup(d => d.GetDataAsync(It.IsAny<Guid>()))
61
+ .ThrowsAsync(new InvalidOperationException("Dependency failed"));
62
+
63
+ // Act & Assert
64
+ await Assert.That(() => _sut.MethodNameAsync(CreateTestInput()))
65
+ .ThrowsException()
66
+ .OfType<ServiceException>();
67
+ }
68
+
69
+ [Test]
70
+ [Arguments(null)]
71
+ [Arguments("")]
72
+ [Arguments(" ")]
73
+ public async Task MethodName_WithInvalidInput_ShouldThrowArgumentException(
74
+ string? invalidInput)
75
+ {
76
+ // Act & Assert
77
+ await Assert.That(() => _sut.MethodNameAsync(invalidInput!))
78
+ .ThrowsException()
79
+ .OfType<ArgumentException>();
80
+ }
81
+
82
+ #endregion
83
+
84
+ #region Test Helpers
85
+
86
+ private static TestInput CreateTestInput() => new TestInput
87
+ {
88
+ Id = Guid.NewGuid(),
89
+ Name = "Test Name",
90
+ CreatedAt = DateTime.UtcNow
91
+ };
92
+
93
+ #endregion
94
+ }
95
+ ```
96
+
97
+ ## Attribute-Based Lifecycle Template
98
+
99
+ TUnit supports attribute-based setup and teardown:
100
+
101
+ ```csharp
102
+ public class DatabaseServiceTests
103
+ {
104
+ private TestDatabase _database = null!;
105
+ private readonly Mock<ILogger<DatabaseService>> _mockLogger;
106
+ private DatabaseService _sut = null!;
107
+
108
+ public DatabaseServiceTests()
109
+ {
110
+ _mockLogger = new Mock<ILogger<DatabaseService>>();
111
+ }
112
+
113
+ [Before(Test)]
114
+ public async Task SetupBeforeEachTest()
115
+ {
116
+ _database = await TestDatabase.CreateAsync();
117
+ _sut = new DatabaseService(_database.ConnectionString, _mockLogger.Object);
118
+ }
119
+
120
+ [After(Test)]
121
+ public async Task CleanupAfterEachTest()
122
+ {
123
+ await _database.DisposeAsync();
124
+ }
125
+
126
+ [Before(Class)]
127
+ public static async Task SetupBeforeAllTests()
128
+ {
129
+ // One-time setup for all tests in class
130
+ }
131
+
132
+ [After(Class)]
133
+ public static async Task CleanupAfterAllTests()
134
+ {
135
+ // One-time cleanup after all tests in class
136
+ }
137
+
138
+ [Test]
139
+ public async Task Query_WithValidSql_ReturnsResults()
140
+ {
141
+ // Test using _database and _sut
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Nested Class Organization Template
147
+
148
+ ```csharp
149
+ public class OrderServiceTests : IAsyncDisposable
150
+ {
151
+ protected readonly Mock<IOrderRepository> _mockRepository;
152
+ protected readonly OrderService _sut;
153
+
154
+ public OrderServiceTests()
155
+ {
156
+ _mockRepository = new Mock<IOrderRepository>();
157
+ _sut = new OrderService(_mockRepository.Object);
158
+ }
159
+
160
+ public ValueTask DisposeAsync() => ValueTask.CompletedTask;
161
+
162
+ public class CreateOrder : OrderServiceTests
163
+ {
164
+ [Test]
165
+ public async Task WithValidOrder_ReturnsSuccessResult()
166
+ {
167
+ var order = new Order { /* ... */ };
168
+ var result = await _sut.CreateOrderAsync(order);
169
+ await Assert.That(result.IsSuccess).IsTrue();
170
+ }
171
+
172
+ [Test]
173
+ public async Task WithNullOrder_ThrowsArgumentNullException()
174
+ {
175
+ await Assert.That(() => _sut.CreateOrderAsync(null!))
176
+ .ThrowsException()
177
+ .OfType<ArgumentNullException>();
178
+ }
179
+ }
180
+
181
+ public class UpdateOrder : OrderServiceTests
182
+ {
183
+ [Test]
184
+ public async Task WithValidChanges_UpdatesSuccessfully()
185
+ {
186
+ // Tests for UpdateOrder
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
192
+ ## ClassDataSource Fixture Template
193
+
194
+ For sharing expensive resources:
195
+
196
+ ```csharp
197
+ // 1. Define the fixture
198
+ public class DatabaseFixture : IAsyncInitializable, IAsyncDisposable
199
+ {
200
+ public TestDatabase Database { get; private set; } = null!;
201
+
202
+ public async Task InitializeAsync()
203
+ {
204
+ Database = await TestDatabase.CreateAsync();
205
+ await Database.SeedTestDataAsync();
206
+ }
207
+
208
+ public async ValueTask DisposeAsync()
209
+ {
210
+ await Database.DisposeAsync();
211
+ }
212
+ }
213
+
214
+ // 2. Use in test classes with ClassDataSource
215
+ public class OrderRepositoryTests
216
+ {
217
+ [Test]
218
+ [ClassDataSource<DatabaseFixture>(Shared = SharedType.Globally)]
219
+ public async Task GetById_ReturnsOrder(DatabaseFixture fixture)
220
+ {
221
+ using var context = new AppDbContext(fixture.Database.ConnectionString);
222
+ var repo = new OrderRepository(context);
223
+
224
+ var result = await repo.GetByIdAsync(TestData.ExistingOrderId);
225
+
226
+ await Assert.That(result).IsNotNull();
227
+ }
228
+ }
229
+ ```
230
+
231
+ ## Parameterized Test Template
232
+
233
+ ```csharp
234
+ public class CalculatorTests
235
+ {
236
+ private readonly Calculator _sut = new();
237
+
238
+ // Simple arguments
239
+ [Test]
240
+ [Arguments(2, 3, 5)]
241
+ [Arguments(-1, 1, 0)]
242
+ [Arguments(0, 0, 0)]
243
+ public async Task Add_WithValidInputs_ReturnsCorrectSum(int a, int b, int expected)
244
+ {
245
+ var result = _sut.Add(a, b);
246
+ await Assert.That(result).IsEqualTo(expected);
247
+ }
248
+
249
+ // With display names for clarity
250
+ [Test]
251
+ [Arguments(10, 2, 5, DisplayName = "Simple division")]
252
+ [Arguments(7, 2, 3.5, DisplayName = "Division with decimal result")]
253
+ [Arguments(-10, 2, -5, DisplayName = "Negative numerator")]
254
+ public async Task Divide_WithValidInputs_ReturnsCorrectResult(
255
+ decimal numerator, decimal denominator, decimal expected)
256
+ {
257
+ var result = _sut.Divide(numerator, denominator);
258
+ await Assert.That(result).IsEqualTo(expected);
259
+ }
260
+
261
+ // Method data source with tuples
262
+ [Test]
263
+ [MethodDataSource(nameof(GetComplexTestCases))]
264
+ public async Task Process_WithComplexInput_ReturnsExpected(
265
+ Order order, bool expectedSuccess)
266
+ {
267
+ var result = await _sut.ProcessAsync(order);
268
+ await Assert.That(result.IsSuccess).IsEqualTo(expectedSuccess);
269
+ }
270
+
271
+ public static IEnumerable<(Order, bool)> GetComplexTestCases()
272
+ {
273
+ yield return (CreateValidOrder(), true);
274
+ yield return (CreateInvalidOrder(), false);
275
+ }
276
+
277
+ // Matrix testing - all combinations
278
+ [Test]
279
+ [Matrix("Small", "Medium", "Large")]
280
+ [Matrix("Standard", "Express")]
281
+ [Matrix(true, false)]
282
+ public async Task CalculateShipping_ReturnsValidPrice(
283
+ string size, string method, bool isInternational)
284
+ {
285
+ var result = await _sut.CalculateShippingAsync(size, method, isInternational);
286
+ await Assert.That(result).IsGreaterThan(0);
287
+ }
288
+ }
289
+ ```
290
+
291
+ ## Logger Testing Template
292
+
293
+ ```csharp
294
+ public class ServiceWithLoggingTests
295
+ {
296
+ private readonly FakeLogger<MyService> _fakeLogger;
297
+ private readonly MyService _sut;
298
+
299
+ public ServiceWithLoggingTests()
300
+ {
301
+ _fakeLogger = new FakeLogger<MyService>();
302
+ _sut = new MyService(_fakeLogger);
303
+ }
304
+
305
+ [Test]
306
+ public async Task ProcessOrder_LogsOrderId()
307
+ {
308
+ // Arrange
309
+ var orderId = 123;
310
+
311
+ // Act
312
+ await _sut.ProcessOrderAsync(orderId);
313
+
314
+ // Assert - Verify structured log properties
315
+ var logEntry = _fakeLogger.Collector.GetSnapshot()
316
+ .Single(r => r.Level == LogLevel.Information);
317
+
318
+ await Assert.That(logEntry.StructuredState).IsNotNull();
319
+ var state = logEntry.StructuredState!.ToDictionary(x => x.Key, x => x.Value);
320
+
321
+ await Assert.That(state).ContainsKey("OrderId");
322
+ await Assert.That(state["OrderId"]).IsEqualTo("123");
323
+ }
324
+
325
+ [Test]
326
+ public async Task ProcessOrder_WhenFails_LogsError()
327
+ {
328
+ // Act
329
+ await Assert.That(() => _sut.ProcessOrderAsync(-1))
330
+ .ThrowsException();
331
+
332
+ // Assert
333
+ var errorLog = _fakeLogger.Collector.GetSnapshot()
334
+ .SingleOrDefault(r => r.Level == LogLevel.Error);
335
+
336
+ await Assert.That(errorLog).IsNotNull();
337
+ }
338
+ }
339
+ ```
340
+
341
+ ## Fluent Assertion Chaining Template
342
+
343
+ ```csharp
344
+ [Test]
345
+ public async Task ComplexResult_MeetsAllExpectations()
346
+ {
347
+ var result = await _sut.ProcessAsync(input);
348
+
349
+ // Chain multiple assertions
350
+ await Assert.That(result)
351
+ .IsNotNull()
352
+ .And.HasProperty(r => r.Items)
353
+ .And.HasCount().GreaterThan(0);
354
+
355
+ // Collection assertions
356
+ await Assert.That(result.Items).HasCount(3);
357
+ await Assert.That(result.Items).Contains(expectedItem);
358
+ await Assert.That(result.Items).IsEquivalentTo(expectedItems);
359
+
360
+ // String assertions
361
+ await Assert.That(result.Message).StartsWith("Success:");
362
+ await Assert.That(result.Message).Contains("processed");
363
+
364
+ // Numeric assertions with tolerance
365
+ await Assert.That(result.Total).IsEqualTo(expected).Within(0.001);
366
+ await Assert.That(result.ProcessedAt)
367
+ .IsEqualTo(DateTime.UtcNow)
368
+ .Within(TimeSpan.FromSeconds(1));
369
+ }
370
+ ```
371
+
372
+ ## Timeout and Retry Template
373
+
374
+ ```csharp
375
+ public class ResilientTests
376
+ {
377
+ [Test]
378
+ [Timeout(5000)] // 5 seconds max
379
+ public async Task LongRunningOperation_CompletesWithinTimeout()
380
+ {
381
+ var result = await _sut.SlowOperationAsync();
382
+ await Assert.That(result).IsNotNull();
383
+ }
384
+
385
+ [Test]
386
+ [Retry(3)] // Retry up to 3 times if fails
387
+ public async Task FlakeyExternalService_EventuallySucceeds()
388
+ {
389
+ var result = await _sut.CallExternalServiceAsync();
390
+ await Assert.That(result.IsSuccess).IsTrue();
391
+ }
392
+
393
+ [Test]
394
+ [Timeout(10000)]
395
+ [Retry(2)]
396
+ public async Task CombinedTimeoutAndRetry()
397
+ {
398
+ // Has 10 seconds to complete, will retry twice on failure
399
+ var result = await _sut.UnreliableOperationAsync();
400
+ await Assert.That(result).IsNotNull();
401
+ }
402
+ }
403
+ ```
404
+
405
+ ## Parallel Control Template
406
+
407
+ ```csharp
408
+ // Tests that must run sequentially
409
+ [NotInParallel]
410
+ public class SequentialTests
411
+ {
412
+ [Test]
413
+ public async Task Test1_ModifiesSharedResource() { }
414
+
415
+ [Test]
416
+ public async Task Test2_AlsoModifiesSharedResource() { }
417
+ }
418
+
419
+ // Tests that share a parallel group (run sequentially within group)
420
+ [ParallelGroup("Database")]
421
+ public class OrderRepositoryTests { }
422
+
423
+ [ParallelGroup("Database")]
424
+ public class CustomerRepositoryTests { }
425
+
426
+ // Custom parallel limit
427
+ [ParallelLimiter<MaxParallel3>]
428
+ public class ResourceIntensiveTests
429
+ {
430
+ [Test]
431
+ public async Task Test1() { }
432
+ }
433
+
434
+ public class MaxParallel3 : IParallelLimit
435
+ {
436
+ public int Limit => 3;
437
+ }
438
+ ```