@grimoire-cc/cli 0.13.3 → 0.15.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.
- package/dist/bin.js +15 -5
- package/dist/bin.js.map +1 -1
- package/dist/commands/agent-paths.d.ts +11 -0
- package/dist/commands/agent-paths.d.ts.map +1 -0
- package/dist/commands/agent-paths.js +69 -0
- package/dist/commands/agent-paths.js.map +1 -0
- package/dist/commands/agent-skills.d.ts +10 -0
- package/dist/commands/agent-skills.d.ts.map +1 -0
- package/dist/commands/agent-skills.js +159 -0
- package/dist/commands/agent-skills.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +62 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +237 -75
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/update.d.ts +1 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +18 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/enforce.d.ts +9 -9
- package/dist/enforce.d.ts.map +1 -1
- package/dist/enforce.js +56 -23
- package/dist/enforce.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +74 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/grimoire-config.d.ts +6 -0
- package/dist/grimoire-config.d.ts.map +1 -0
- package/dist/grimoire-config.js +23 -0
- package/dist/grimoire-config.js.map +1 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +13 -8
- package/dist/prompt.js.map +1 -1
- package/dist/remove.d.ts +4 -0
- package/dist/remove.d.ts.map +1 -1
- package/dist/remove.js +8 -0
- package/dist/remove.js.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +12 -5
- package/dist/resolve.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +45 -2
- package/dist/setup.js.map +1 -1
- package/dist/summary.d.ts.map +1 -1
- package/dist/summary.js +9 -0
- package/dist/summary.js.map +1 -1
- package/package.json +1 -1
- package/packs/dev-pack/agents/grimoire.tdd-specialist.md +194 -27
- package/packs/dev-pack/grimoire.json +0 -38
- package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +69 -65
- package/packs/dotnet-pack/agents/grimoire.csharp-coder.md +110 -113
- package/packs/dotnet-pack/grimoire.json +23 -5
- package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/SKILL.md +252 -0
- package/packs/{dev-pack/skills/grimoire.tdd-specialist → dotnet-pack/skills/grimoire.unit-testing-dotnet}/reference/anti-patterns.md +78 -0
- package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/reference/tdd-workflow-patterns.md +259 -0
- package/packs/frontend-pack/agents/grimoire.angular-coder.md +193 -0
- package/packs/frontend-pack/grimoire.json +7 -0
- package/packs/go-pack/grimoire.json +19 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/SKILL.md +256 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/reference/anti-patterns.md +244 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/reference/tdd-workflow-patterns.md +259 -0
- package/packs/python-pack/grimoire.json +19 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/SKILL.md +239 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/reference/anti-patterns.md +244 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/reference/tdd-workflow-patterns.md +259 -0
- package/packs/rust-pack/grimoire.json +29 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/SKILL.md +243 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/anti-patterns.md +244 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/tdd-workflow-patterns.md +259 -0
- package/packs/ts-pack/agents/grimoire.typescript-coder.md +36 -1
- package/packs/ts-pack/grimoire.json +27 -1
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/SKILL.md +255 -0
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/anti-patterns.md +244 -0
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/tdd-workflow-patterns.md +259 -0
- package/dist/commands/enforce-agent.d.ts +0 -5
- package/dist/commands/enforce-agent.d.ts.map +0 -1
- package/dist/commands/enforce-agent.js +0 -94
- package/dist/commands/enforce-agent.js.map +0 -1
- package/packs/dev-pack/skills/grimoire.tdd-specialist/SKILL.md +0 -248
- package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/language-frameworks.md +0 -388
- package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/tdd-workflow-patterns.md +0 -135
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +0 -293
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +0 -329
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +0 -361
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +0 -378
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +0 -476
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +0 -576
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +0 -438
- 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
|
package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md
DELETED
|
@@ -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
|
-
```
|