@grimoire-cc/cli 0.13.2 → 0.14.0

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.
Files changed (50) hide show
  1. package/dist/commands/update.d.ts.map +1 -1
  2. package/dist/commands/update.js +14 -0
  3. package/dist/commands/update.js.map +1 -1
  4. package/dist/enforce.d.ts +3 -1
  5. package/dist/enforce.d.ts.map +1 -1
  6. package/dist/enforce.js +18 -6
  7. package/dist/enforce.js.map +1 -1
  8. package/dist/setup.d.ts.map +1 -1
  9. package/dist/setup.js +47 -0
  10. package/dist/setup.js.map +1 -1
  11. package/dist/summary.d.ts.map +1 -1
  12. package/dist/summary.js +9 -0
  13. package/dist/summary.js.map +1 -1
  14. package/package.json +1 -1
  15. package/packs/dev-pack/agents/grimoire.tdd-specialist.md +194 -27
  16. package/packs/dev-pack/grimoire.json +9 -42
  17. package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +68 -47
  18. package/packs/dotnet-pack/agents/grimoire.csharp-coder.md +110 -113
  19. package/packs/dotnet-pack/grimoire.json +23 -5
  20. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/SKILL.md +252 -0
  21. package/packs/{dev-pack/skills/grimoire.tdd-specialist → dotnet-pack/skills/grimoire.unit-testing-dotnet}/reference/anti-patterns.md +78 -0
  22. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/reference/tdd-workflow-patterns.md +259 -0
  23. package/packs/go-pack/grimoire.json +19 -0
  24. package/packs/go-pack/skills/grimoire.unit-testing-go/SKILL.md +256 -0
  25. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/anti-patterns.md +244 -0
  26. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/tdd-workflow-patterns.md +259 -0
  27. package/packs/python-pack/grimoire.json +19 -0
  28. package/packs/python-pack/skills/grimoire.unit-testing-python/SKILL.md +239 -0
  29. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/anti-patterns.md +244 -0
  30. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/tdd-workflow-patterns.md +259 -0
  31. package/packs/rust-pack/grimoire.json +29 -0
  32. package/packs/rust-pack/skills/grimoire.unit-testing-rust/SKILL.md +243 -0
  33. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/anti-patterns.md +244 -0
  34. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/tdd-workflow-patterns.md +259 -0
  35. package/packs/ts-pack/agents/grimoire.typescript-coder.md +36 -1
  36. package/packs/ts-pack/grimoire.json +27 -1
  37. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/SKILL.md +255 -0
  38. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/anti-patterns.md +244 -0
  39. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/tdd-workflow-patterns.md +259 -0
  40. package/packs/dev-pack/skills/grimoire.tdd-specialist/SKILL.md +0 -248
  41. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/language-frameworks.md +0 -388
  42. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/tdd-workflow-patterns.md +0 -135
  43. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +0 -293
  44. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +0 -329
  45. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +0 -361
  46. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +0 -378
  47. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +0 -476
  48. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +0 -576
  49. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +0 -438
  50. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/xunit-template.md +0 -303
@@ -1,329 +0,0 @@
1
- # Anti-Patterns to Avoid
2
-
3
- Common testing mistakes and how to fix them.
4
-
5
- ## Table of Contents
6
-
7
- - [Mock Implementation in TDD](#mock-implementation-in-tdd)
8
- - [Over-Specification](#over-specification)
9
- - [Blocking on Async](#blocking-on-async)
10
- - [Shared Mutable State](#shared-mutable-state)
11
- - [Testing Implementation Details](#testing-implementation-details)
12
- - [Vague Assertions](#vague-assertions)
13
- - [Summary Checklist](#summary-checklist)
14
-
15
- ---
16
-
17
- ## Mock Implementation in TDD
18
-
19
- **Don't**: Create mock/stub implementations during TDD before the real implementation exists.
20
-
21
- ```csharp
22
- // BAD: Creating a fake implementation to make tests pass
23
- public class FakeDocumentService : IDocumentService
24
- {
25
- public Task<Document> GetAsync(Guid id) => Task.FromResult(new Document());
26
- }
27
- ```
28
-
29
- **Why it's wrong**: In TDD, tests should FAIL first against non-existent functionality. Creating fake implementations defeats the purpose of TDD and doesn't verify the real code.
30
-
31
- **Instead**: Write the test, verify it fails (because the implementation doesn't exist), THEN implement the real code.
32
-
33
- ```csharp
34
- // CORRECT TDD approach:
35
- // 1. Write the test
36
- [Fact]
37
- public async Task GetDocument_WithValidId_ReturnsDocument()
38
- {
39
- var result = await _sut.GetDocumentAsync(validId);
40
- Assert.NotNull(result);
41
- }
42
-
43
- // 2. Run test - it FAILS (method not implemented)
44
- // 3. Implement the REAL code in DocumentService
45
- // 4. Run test - it PASSES
46
- ```
47
-
48
- ---
49
-
50
- ## Over-Specification
51
-
52
- **Don't**: Verify every single mock interaction.
53
-
54
- ```csharp
55
- // BAD: Over-specified test - brittle and tests implementation, not behavior
56
- [Fact]
57
- public async Task ProcessOrder_ShouldCallAllDependencies()
58
- {
59
- await _sut.ProcessOrderAsync(order);
60
-
61
- _mockRepo.Verify(r => r.GetByIdAsync(It.IsAny<Guid>()), Times.Once);
62
- _mockRepo.Verify(r => r.SaveAsync(It.IsAny<Order>()), Times.Once);
63
- _mockValidator.Verify(v => v.ValidateAsync(It.IsAny<Order>()), Times.Once);
64
- _mockLogger.Verify(l => l.Log(It.IsAny<LogLevel>(), /*...*/), Times.Exactly(3));
65
- _mockNotifier.Verify(n => n.SendAsync(It.IsAny<Notification>()), Times.Once);
66
- }
67
- ```
68
-
69
- **Why it's wrong**: Tests become brittle, break on any refactoring, and test implementation details rather than behavior.
70
-
71
- **Instead**: Only verify interactions that are essential to the test's behavioral purpose.
72
-
73
- ```csharp
74
- // CORRECT: Verify only essential outcomes
75
- [Fact]
76
- public async Task ProcessOrder_WithValidOrder_SavesOrderWithProcessedStatus()
77
- {
78
- await _sut.ProcessOrderAsync(order);
79
-
80
- // Only verify the essential behavioral outcome
81
- _mockRepo.Verify(r => r.SaveAsync(
82
- It.Is<Order>(o => o.Status == OrderStatus.Processed)),
83
- Times.Once);
84
- }
85
- ```
86
-
87
- ---
88
-
89
- ## Blocking on Async
90
-
91
- **Don't**: Use `.Result` or `.Wait()` on async operations.
92
-
93
- ```csharp
94
- // BAD: Blocking on async - can deadlock, hides async behavior
95
- [Fact]
96
- public void GetOrder_ShouldReturnOrder()
97
- {
98
- var result = _sut.GetOrderAsync(id).Result; // WRONG
99
- Assert.NotNull(result);
100
- }
101
-
102
- // ALSO BAD
103
- [Fact]
104
- public void GetOrder_ShouldReturnOrder()
105
- {
106
- _sut.GetOrderAsync(id).Wait(); // WRONG
107
- }
108
- ```
109
-
110
- **Why it's wrong**: Can cause deadlocks, doesn't test true async behavior, and hides potential async issues.
111
-
112
- **Instead**: Use `async Task` and `await`.
113
-
114
- ```csharp
115
- // CORRECT
116
- [Fact]
117
- public async Task GetOrder_ShouldReturnOrder()
118
- {
119
- var result = await _sut.GetOrderAsync(id);
120
- Assert.NotNull(result);
121
- }
122
- ```
123
-
124
- ---
125
-
126
- ## Shared Mutable State
127
-
128
- **Don't**: Share mutable state between tests.
129
-
130
- ```csharp
131
- // BAD: Static shared state causes test pollution
132
- public class OrderServiceTests
133
- {
134
- private static List<Order> _orders = new List<Order>(); // WRONG
135
- private static Mock<IRepository> _sharedMock = new Mock<IRepository>(); // WRONG
136
- private static int _testCounter = 0; // WRONG
137
-
138
- [Fact]
139
- public void Test1()
140
- {
141
- _orders.Add(new Order()); // Affects other tests!
142
- _testCounter++;
143
- }
144
-
145
- [Fact]
146
- public void Test2()
147
- {
148
- Assert.Empty(_orders); // May fail if Test1 runs first!
149
- }
150
- }
151
- ```
152
-
153
- **Why it's wrong**: Tests become order-dependent, can't run in parallel, and failures are hard to diagnose.
154
-
155
- **Instead**: Create fresh instances in the constructor for each test.
156
-
157
- ```csharp
158
- // CORRECT: Fresh instances per test
159
- public class OrderServiceTests
160
- {
161
- private readonly List<Order> _orders; // Instance field
162
- private readonly Mock<IRepository> _mockRepo;
163
-
164
- public OrderServiceTests()
165
- {
166
- _orders = new List<Order>(); // Fresh for each test
167
- _mockRepo = new Mock<IRepository>(); // Fresh for each test
168
- }
169
-
170
- [Fact]
171
- public void Test1()
172
- {
173
- _orders.Add(new Order());
174
- }
175
-
176
- [Fact]
177
- public void Test2()
178
- {
179
- Assert.Empty(_orders); // Always passes - fresh list
180
- }
181
- }
182
- ```
183
-
184
- ---
185
-
186
- ## Testing Implementation Details
187
-
188
- **Don't**: Test private methods or internal implementation details.
189
-
190
- ```csharp
191
- // BAD: Testing private method via reflection
192
- [Fact]
193
- public void PrivateCalculateHash_ShouldReturnValidHash()
194
- {
195
- var method = typeof(UserService)
196
- .GetMethod("CalculateHash", BindingFlags.NonPublic | BindingFlags.Instance);
197
- var result = method.Invoke(_sut, new object[] { "input" });
198
- // ...
199
- }
200
-
201
- // BAD: Testing internal state
202
- [Fact]
203
- public void ProcessOrder_ShouldSetInternalFlag()
204
- {
205
- _sut.ProcessOrder(order);
206
-
207
- var field = typeof(OrderService)
208
- .GetField("_processingComplete", BindingFlags.NonPublic | BindingFlags.Instance);
209
- var value = (bool)field.GetValue(_sut);
210
- Assert.True(value);
211
- }
212
- ```
213
-
214
- **Why it's wrong**: Tests become coupled to implementation, break on refactoring, and don't test actual behavior.
215
-
216
- **Instead**: Test through public interfaces. If you need to test a private method, it might belong in its own class.
217
-
218
- ```csharp
219
- // CORRECT: Test through public interface
220
- [Fact]
221
- public async Task ProcessOrder_WithValidOrder_ReturnsSuccessResult()
222
- {
223
- var result = await _sut.ProcessOrderAsync(order);
224
-
225
- Assert.True(result.IsSuccess);
226
- Assert.Equal(OrderStatus.Processed, result.Order.Status);
227
- }
228
-
229
- // If private logic is complex enough to test directly,
230
- // extract it to its own class
231
- public class HashCalculator : IHashCalculator
232
- {
233
- public string Calculate(string input) { /* ... */ }
234
- }
235
-
236
- [Fact]
237
- public void Calculate_WithInput_ReturnsValidHash()
238
- {
239
- var calculator = new HashCalculator();
240
- var result = calculator.Calculate("input");
241
- Assert.NotEmpty(result);
242
- }
243
- ```
244
-
245
- ---
246
-
247
- ## Vague Assertions
248
-
249
- **Don't**: Use vague or missing assertions.
250
-
251
- ```csharp
252
- // BAD: No meaningful assertion
253
- [Fact]
254
- public async Task ProcessOrder_ShouldWork()
255
- {
256
- var result = await _sut.ProcessOrderAsync(order);
257
- Assert.NotNull(result); // Too vague - what should result contain?
258
- }
259
-
260
- // BAD: Testing that no exception was thrown (usually pointless)
261
- [Fact]
262
- public async Task ProcessOrder_ShouldNotThrow()
263
- {
264
- var exception = await Record.ExceptionAsync(() => _sut.ProcessOrderAsync(order));
265
- Assert.Null(exception);
266
- }
267
-
268
- // BAD: Using Assert.True with no context
269
- [Fact]
270
- public void Validate_ShouldPass()
271
- {
272
- var result = _sut.Validate(input);
273
- Assert.True(result); // If this fails, what went wrong?
274
- }
275
- ```
276
-
277
- **Why it's wrong**: Tests pass but don't verify meaningful behavior. False confidence in code correctness.
278
-
279
- **Instead**: Assert on specific expected values and behaviors.
280
-
281
- ```csharp
282
- // CORRECT: Specific assertions
283
- [Fact]
284
- public async Task ProcessOrder_WithValidOrder_ReturnsProcessedOrderWithTimestamp()
285
- {
286
- var result = await _sut.ProcessOrderAsync(order);
287
-
288
- Assert.NotNull(result);
289
- Assert.Equal(OrderStatus.Processed, result.Status);
290
- Assert.NotNull(result.ProcessedAt);
291
- Assert.Equal(order.Id, result.OrderId);
292
- }
293
-
294
- // CORRECT: Assert on validation result with context
295
- [Fact]
296
- public void Validate_WithValidInput_ReturnsSuccessWithNoErrors()
297
- {
298
- var result = _sut.Validate(input);
299
-
300
- Assert.True(result.IsValid, $"Expected valid but got errors: {string.Join(", ", result.Errors)}");
301
- Assert.Empty(result.Errors);
302
- }
303
-
304
- // CORRECT: When testing that exceptions ARE thrown
305
- [Fact]
306
- public async Task ProcessOrder_WithNullOrder_ThrowsArgumentNullException()
307
- {
308
- var exception = await Assert.ThrowsAsync<ArgumentNullException>(
309
- () => _sut.ProcessOrderAsync(null!));
310
-
311
- Assert.Equal("order", exception.ParamName);
312
- }
313
- ```
314
-
315
- ---
316
-
317
- ## Summary Checklist
318
-
319
- Before committing tests, verify:
320
-
321
- - [ ] No `.Result` or `.Wait()` on async operations
322
- - [ ] No static mutable state shared between tests
323
- - [ ] Not testing private methods via reflection
324
- - [ ] Not verifying every mock interaction (only essential ones)
325
- - [ ] Assertions are specific and meaningful
326
- - [ ] Tests fail before implementation (TDD)
327
- - [ ] Tests don't depend on execution order
328
- - [ ] No `Thread.Sleep` (use async waits instead)
329
- - [ ] Test names describe behavior, not implementation
@@ -1,361 +0,0 @@
1
- # Framework Guidelines
2
-
3
- Detailed guidelines for xUnit and TUnit testing frameworks.
4
-
5
- ## Table of Contents
6
-
7
- - [xUnit Guidelines](#xunit-guidelines)
8
- - [Attributes](#xunit-attributes)
9
- - [Lifecycle](#xunit-lifecycle)
10
- - [Assertions](#xunit-assertions)
11
- - [TUnit Guidelines](#tunit-guidelines)
12
- - [Attributes](#tunit-attributes)
13
- - [Lifecycle](#tunit-lifecycle)
14
- - [Assertions](#tunit-assertions)
15
- - [Assertion Chaining](#tunit-assertion-chaining)
16
-
17
- ---
18
-
19
- ## xUnit Guidelines
20
-
21
- xUnit is the default framework for .NET testing. It uses constructor injection for test setup and implements `IDisposable` for cleanup.
22
-
23
- ### xUnit Attributes
24
-
25
- | Attribute | Purpose |
26
- | ----------- | --------- |
27
- | `[Fact]` | Single test case |
28
- | `[Theory]` | Parameterized test |
29
- | `[InlineData]` | Simple inline test data |
30
- | `[MemberData]` | Method-based test data |
31
- | `[ClassData]` | Reusable test data class |
32
- | `[Trait]` | Test categorization |
33
- | `[Collection]` | Shared fixture across classes |
34
-
35
- **Examples:**
36
-
37
- ```csharp
38
- // Simple test
39
- [Fact]
40
- public async Task GetOrder_WithValidId_ReturnsOrder()
41
- {
42
- var result = await _sut.GetOrderAsync(validId);
43
- Assert.NotNull(result);
44
- }
45
-
46
- // Parameterized test with inline data
47
- [Theory]
48
- [InlineData("", false)]
49
- [InlineData("invalid", false)]
50
- [InlineData("test@example.com", true)]
51
- public void IsValidEmail_WithVariousInputs_ReturnsExpectedResult(string email, bool expected)
52
- {
53
- var result = _sut.IsValidEmail(email);
54
- Assert.Equal(expected, result);
55
- }
56
-
57
- // Parameterized test with method data
58
- [Theory]
59
- [MemberData(nameof(GetOrderTestCases))]
60
- public async Task ProcessOrder_WithVariousOrders_BehavesCorrectly(
61
- Order order, bool expectedSuccess, string expectedMessage)
62
- {
63
- var result = await _sut.ProcessOrderAsync(order);
64
- Assert.Equal(expectedSuccess, result.Success);
65
- }
66
-
67
- public static IEnumerable<object[]> GetOrderTestCases()
68
- {
69
- yield return new object[] { CreateValidOrder(), true, "Success" };
70
- yield return new object[] { CreateInvalidOrder(), false, "Invalid order" };
71
- }
72
- ```
73
-
74
- ### xUnit Lifecycle
75
-
76
- ```csharp
77
- public class OrderServiceTests : IDisposable
78
- {
79
- // Fresh instances for each test via constructor
80
- private readonly Mock<IOrderRepository> _mockRepository;
81
- private readonly FakeLogger<OrderService> _fakeLogger;
82
- private readonly OrderService _sut;
83
-
84
- public OrderServiceTests()
85
- {
86
- _mockRepository = new Mock<IOrderRepository>();
87
- _fakeLogger = new FakeLogger<OrderService>();
88
- _sut = new OrderService(_fakeLogger, _mockRepository.Object);
89
- }
90
-
91
- public void Dispose()
92
- {
93
- // Cleanup if needed
94
- }
95
- }
96
- ```
97
-
98
- **Async Lifecycle:**
99
-
100
- ```csharp
101
- public class DatabaseTests : IAsyncLifetime
102
- {
103
- private TestDatabase _database;
104
-
105
- public async Task InitializeAsync()
106
- {
107
- _database = await TestDatabase.CreateAsync();
108
- }
109
-
110
- public async Task DisposeAsync()
111
- {
112
- await _database.CleanupAsync();
113
- }
114
- }
115
- ```
116
-
117
- ### xUnit Assertions
118
-
119
- ```csharp
120
- // Basic assertions
121
- Assert.True(condition);
122
- Assert.False(condition);
123
- Assert.Null(value);
124
- Assert.NotNull(value);
125
- Assert.Equal(expected, actual);
126
- Assert.NotEqual(expected, actual);
127
-
128
- // Collection assertions
129
- Assert.Empty(collection);
130
- Assert.NotEmpty(collection);
131
- Assert.Contains(item, collection);
132
- Assert.DoesNotContain(item, collection);
133
- Assert.All(collection, item => Assert.True(item.IsValid));
134
- Assert.Single(collection);
135
-
136
- // String assertions
137
- Assert.StartsWith("prefix", value);
138
- Assert.EndsWith("suffix", value);
139
- Assert.Contains("substring", value);
140
-
141
- // Exception assertions
142
- await Assert.ThrowsAsync<ArgumentNullException>(() => _sut.MethodAsync(null));
143
- var ex = await Assert.ThrowsAsync<CustomException>(() => _sut.MethodAsync(input));
144
- Assert.Equal("expected message", ex.Message);
145
-
146
- // Type assertions
147
- Assert.IsType<ExpectedType>(value);
148
- Assert.IsAssignableFrom<IInterface>(value);
149
- ```
150
-
151
- ---
152
-
153
- ## TUnit Guidelines
154
-
155
- TUnit is recommended for new .NET 8+ projects. It's async-first with built-in fluent assertions.
156
-
157
- ### TUnit Attributes
158
-
159
- | Attribute | Purpose | xUnit Equivalent |
160
- | ----------- | --------- | ------------------ |
161
- | `[Test]` | All test methods | `[Fact]` |
162
- | `[Arguments]` | Inline test data | `[InlineData]` |
163
- | `[MethodDataSource]` | Method-based data | `[MemberData]` |
164
- | `[ClassDataSource]` | Reusable data class | `[ClassData]` |
165
- | `[Matrix]` | Combinatorial testing | N/A (unique to TUnit) |
166
- | `[Category]` | Test categorization | `[Trait]` |
167
- | `[Timeout]` | Test timeout | N/A |
168
- | `[Retry]` | Retry flaky tests | N/A |
169
- | `[NotInParallel]` | Sequential execution | `[Collection]` |
170
-
171
- **Examples:**
172
-
173
- ```csharp
174
- // Simple test
175
- [Test]
176
- public async Task GetOrder_WithValidId_ReturnsOrder()
177
- {
178
- var result = await _sut.GetOrderAsync(validId);
179
- await Assert.That(result).IsNotNull();
180
- }
181
-
182
- // Parameterized test with arguments
183
- [Test]
184
- [Arguments("", false)]
185
- [Arguments("invalid", false)]
186
- [Arguments("test@example.com", true)]
187
- public async Task IsValidEmail_WithVariousInputs_ReturnsExpectedResult(string email, bool expected)
188
- {
189
- var result = _sut.IsValidEmail(email);
190
- await Assert.That(result).IsEqualTo(expected);
191
- }
192
-
193
- // Parameterized test with method data source
194
- [Test]
195
- [MethodDataSource(nameof(GetOrderTestCases))]
196
- public async Task ProcessOrder_WithVariousOrders_BehavesCorrectly(Order order, bool expectedSuccess)
197
- {
198
- var result = await _sut.ProcessOrderAsync(order);
199
- await Assert.That(result.IsSuccess).IsEqualTo(expectedSuccess);
200
- }
201
-
202
- // TUnit supports tuples for cleaner syntax
203
- public static IEnumerable<(Order, bool)> GetOrderTestCases()
204
- {
205
- yield return (CreateValidOrder(), true);
206
- yield return (CreateInvalidOrder(), false);
207
- }
208
- ```
209
-
210
- ### TUnit Lifecycle
211
-
212
- ```csharp
213
- public class OrderServiceTests : IAsyncDisposable
214
- {
215
- private readonly Mock<IOrderRepository> _mockRepository;
216
- private readonly FakeLogger<OrderService> _fakeLogger;
217
- private readonly OrderService _sut;
218
-
219
- public OrderServiceTests()
220
- {
221
- _mockRepository = new Mock<IOrderRepository>();
222
- _fakeLogger = new FakeLogger<OrderService>();
223
- _sut = new OrderService(_fakeLogger, _mockRepository.Object);
224
- }
225
-
226
- public ValueTask DisposeAsync()
227
- {
228
- // Cleanup if needed
229
- return ValueTask.CompletedTask;
230
- }
231
- }
232
- ```
233
-
234
- **Attribute-based Lifecycle:**
235
-
236
- ```csharp
237
- public class DatabaseTests
238
- {
239
- [Before(Test)]
240
- public async Task SetupBeforeEachTest() { }
241
-
242
- [After(Test)]
243
- public async Task CleanupAfterEachTest() { }
244
-
245
- [Before(Class)]
246
- public static async Task SetupBeforeAllTests() { }
247
-
248
- [After(Class)]
249
- public static async Task CleanupAfterAllTests() { }
250
- }
251
- ```
252
-
253
- ### TUnit Assertions
254
-
255
- All TUnit assertions are awaitable, providing better stack traces and true async behavior.
256
-
257
- ```csharp
258
- // Basic assertions
259
- await Assert.That(condition).IsTrue();
260
- await Assert.That(condition).IsFalse();
261
- await Assert.That(value).IsNull();
262
- await Assert.That(value).IsNotNull();
263
- await Assert.That(actual).IsEqualTo(expected);
264
- await Assert.That(actual).IsNotEqualTo(expected);
265
-
266
- // Collection assertions
267
- await Assert.That(collection).IsEmpty();
268
- await Assert.That(collection).IsNotEmpty();
269
- await Assert.That(collection).Contains("item");
270
- await Assert.That(collection).DoesNotContain("item");
271
- await Assert.That(collection).HasCount(3);
272
- await Assert.That(collection).IsEquivalentTo(expectedItems);
273
-
274
- // String assertions
275
- await Assert.That(message).StartsWith("Error:");
276
- await Assert.That(message).EndsWith("failed");
277
- await Assert.That(message).Contains("substring");
278
-
279
- // Numeric assertions with tolerance
280
- await Assert.That(result).IsEqualTo(expected).Within(0.001);
281
- await Assert.That(timestamp).IsEqualTo(DateTime.Now).Within(TimeSpan.FromSeconds(1));
282
-
283
- // Exception assertions
284
- await Assert.That(() => _sut.GetOrderAsync(invalidId))
285
- .ThrowsException()
286
- .OfType<OrderNotFoundException>()
287
- .WithProperty(e => e.OrderId, invalidId);
288
-
289
- // Type assertions
290
- await Assert.That(value).IsTypeOf<ExpectedType>();
291
- await Assert.That(value).IsAssignableTo<IInterface>();
292
- ```
293
-
294
- ### TUnit Assertion Chaining
295
-
296
- TUnit allows fluent chaining with `.And`:
297
-
298
- ```csharp
299
- // Chain multiple assertions
300
- await Assert.That(result)
301
- .IsNotNull()
302
- .And.HasProperty(r => r.Items)
303
- .And.HasCount().GreaterThan(0);
304
-
305
- // Numeric range
306
- await Assert.That(result).IsGreaterThan(0);
307
- await Assert.That(result).IsLessThan(1000);
308
- await Assert.That(result).IsBetween(1, 100);
309
- ```
310
-
311
- ### TUnit-Specific Features
312
-
313
- **Timeout:**
314
-
315
- ```csharp
316
- [Test]
317
- [Timeout(5000)] // 5 seconds
318
- public async Task LongRunningOperation_CompletesWithinTimeout()
319
- {
320
- var result = await _sut.ProcessAsync();
321
- await Assert.That(result.IsSuccess).IsTrue();
322
- }
323
- ```
324
-
325
- **Retry:**
326
-
327
- ```csharp
328
- [Test]
329
- [Retry(3)]
330
- public async Task OccasionallySlowTest()
331
- {
332
- // Will retry up to 3 times if it fails
333
- }
334
- ```
335
-
336
- **Matrix Testing (Combinatorial):**
337
-
338
- ```csharp
339
- // Tests all combinations: 3 sizes × 2 methods × 2 booleans = 12 tests
340
- [Test]
341
- [Matrix("Small", "Medium", "Large")]
342
- [Matrix("Standard", "Express")]
343
- [Matrix(true, false)]
344
- public async Task CalculateShipping_MatrixTest(string size, string method, bool isInternational)
345
- {
346
- var result = await _sut.CalculateShippingAsync(size, method, isInternational);
347
- await Assert.That(result).IsGreaterThan(0);
348
- }
349
- ```
350
-
351
- **Custom Display Names:**
352
-
353
- ```csharp
354
- [Test]
355
- [Arguments("", false, DisplayName = "Empty string should be invalid")]
356
- [Arguments("test@example.com", true, DisplayName = "Standard email should be valid")]
357
- public async Task IsValidEmail_WithDisplayNames(string email, bool expected)
358
- {
359
- await Assert.That(_sut.IsValid(email)).IsEqualTo(expected);
360
- }
361
- ```