@grimoire-cc/cli 0.13.1 → 0.13.3
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/package.json +1 -1
- package/packs/dev-pack/grimoire.json +9 -4
- package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +25 -8
- package/packs/dotnet-pack/grimoire.json +6 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +293 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +329 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +361 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +378 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +476 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +576 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +438 -0
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/xunit-template.md +303 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# xUnit Test File Template
|
|
2
|
+
|
|
3
|
+
Standard template for xUnit test files.
|
|
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 Xunit;
|
|
12
|
+
|
|
13
|
+
namespace YourProject.Tests.Services;
|
|
14
|
+
|
|
15
|
+
public class YourServiceTests : IDisposable
|
|
16
|
+
{
|
|
17
|
+
private readonly FakeLogger<YourService> _fakeLogger;
|
|
18
|
+
private readonly Mock<IDependency> _mockDependency;
|
|
19
|
+
private readonly YourService _sut; // System Under Test
|
|
20
|
+
|
|
21
|
+
public YourServiceTests()
|
|
22
|
+
{
|
|
23
|
+
_fakeLogger = new FakeLogger<YourService>();
|
|
24
|
+
_mockDependency = new Mock<IDependency>();
|
|
25
|
+
_sut = new YourService(_fakeLogger, _mockDependency.Object);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public void Dispose()
|
|
29
|
+
{
|
|
30
|
+
// Cleanup resources if needed
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#region MethodName Tests
|
|
34
|
+
|
|
35
|
+
[Fact]
|
|
36
|
+
public async Task MethodName_WhenConditionIsTrue_ShouldReturnExpectedResult()
|
|
37
|
+
{
|
|
38
|
+
// Arrange
|
|
39
|
+
var input = CreateTestInput();
|
|
40
|
+
_mockDependency
|
|
41
|
+
.Setup(d => d.GetDataAsync(It.IsAny<Guid>()))
|
|
42
|
+
.ReturnsAsync(expectedData);
|
|
43
|
+
|
|
44
|
+
// Act
|
|
45
|
+
var result = await _sut.MethodNameAsync(input);
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
Assert.NotNull(result);
|
|
49
|
+
Assert.Equal(expected, result.Property);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[Fact]
|
|
53
|
+
public async Task MethodName_WhenDependencyFails_ShouldThrowException()
|
|
54
|
+
{
|
|
55
|
+
// Arrange
|
|
56
|
+
_mockDependency
|
|
57
|
+
.Setup(d => d.GetDataAsync(It.IsAny<Guid>()))
|
|
58
|
+
.ThrowsAsync(new InvalidOperationException("Dependency failed"));
|
|
59
|
+
|
|
60
|
+
// Act & Assert
|
|
61
|
+
await Assert.ThrowsAsync<ServiceException>(
|
|
62
|
+
() => _sut.MethodNameAsync(CreateTestInput()));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[Theory]
|
|
66
|
+
[InlineData(null)]
|
|
67
|
+
[InlineData("")]
|
|
68
|
+
[InlineData(" ")]
|
|
69
|
+
public async Task MethodName_WithInvalidInput_ShouldThrowArgumentException(
|
|
70
|
+
string? invalidInput)
|
|
71
|
+
{
|
|
72
|
+
// Act & Assert
|
|
73
|
+
await Assert.ThrowsAsync<ArgumentException>(
|
|
74
|
+
() => _sut.MethodNameAsync(invalidInput!));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#endregion
|
|
78
|
+
|
|
79
|
+
#region Test Helpers
|
|
80
|
+
|
|
81
|
+
private static TestInput CreateTestInput() => new TestInput
|
|
82
|
+
{
|
|
83
|
+
Id = Guid.NewGuid(),
|
|
84
|
+
Name = "Test Name",
|
|
85
|
+
CreatedAt = DateTime.UtcNow
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
#endregion
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Async Lifecycle Template
|
|
93
|
+
|
|
94
|
+
For tests requiring async setup/teardown:
|
|
95
|
+
|
|
96
|
+
```csharp
|
|
97
|
+
public class DatabaseServiceTests : IAsyncLifetime
|
|
98
|
+
{
|
|
99
|
+
private TestDatabase _database = null!;
|
|
100
|
+
private readonly Mock<ILogger<DatabaseService>> _mockLogger;
|
|
101
|
+
private DatabaseService _sut = null!;
|
|
102
|
+
|
|
103
|
+
public DatabaseServiceTests()
|
|
104
|
+
{
|
|
105
|
+
_mockLogger = new Mock<ILogger<DatabaseService>>();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public async Task InitializeAsync()
|
|
109
|
+
{
|
|
110
|
+
_database = await TestDatabase.CreateAsync();
|
|
111
|
+
_sut = new DatabaseService(_database.ConnectionString, _mockLogger.Object);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public async Task DisposeAsync()
|
|
115
|
+
{
|
|
116
|
+
await _database.DisposeAsync();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
[Fact]
|
|
120
|
+
public async Task Query_WithValidSql_ReturnsResults()
|
|
121
|
+
{
|
|
122
|
+
// Test using _database and _sut
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Nested Class Organization Template
|
|
128
|
+
|
|
129
|
+
```csharp
|
|
130
|
+
public class OrderServiceTests : IDisposable
|
|
131
|
+
{
|
|
132
|
+
private readonly Mock<IOrderRepository> _mockRepository;
|
|
133
|
+
private readonly OrderService _sut;
|
|
134
|
+
|
|
135
|
+
public OrderServiceTests()
|
|
136
|
+
{
|
|
137
|
+
_mockRepository = new Mock<IOrderRepository>();
|
|
138
|
+
_sut = new OrderService(_mockRepository.Object);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public void Dispose() { }
|
|
142
|
+
|
|
143
|
+
public class CreateOrder : OrderServiceTests
|
|
144
|
+
{
|
|
145
|
+
[Fact]
|
|
146
|
+
public async Task WithValidOrder_ReturnsSuccessResult()
|
|
147
|
+
{
|
|
148
|
+
var order = new Order { /* ... */ };
|
|
149
|
+
var result = await _sut.CreateOrderAsync(order);
|
|
150
|
+
Assert.True(result.IsSuccess);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
[Fact]
|
|
154
|
+
public async Task WithNullOrder_ThrowsArgumentNullException()
|
|
155
|
+
{
|
|
156
|
+
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
157
|
+
() => _sut.CreateOrderAsync(null!));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public class UpdateOrder : OrderServiceTests
|
|
162
|
+
{
|
|
163
|
+
[Fact]
|
|
164
|
+
public async Task WithValidChanges_UpdatesSuccessfully()
|
|
165
|
+
{
|
|
166
|
+
// Tests for UpdateOrder
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Collection Fixture Template
|
|
173
|
+
|
|
174
|
+
For sharing expensive resources across test classes:
|
|
175
|
+
|
|
176
|
+
```csharp
|
|
177
|
+
// 1. Define the fixture
|
|
178
|
+
public class DatabaseFixture : IAsyncLifetime
|
|
179
|
+
{
|
|
180
|
+
public TestDatabase Database { get; private set; } = null!;
|
|
181
|
+
|
|
182
|
+
public async Task InitializeAsync()
|
|
183
|
+
{
|
|
184
|
+
Database = await TestDatabase.CreateAsync();
|
|
185
|
+
await Database.SeedTestDataAsync();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public async Task DisposeAsync()
|
|
189
|
+
{
|
|
190
|
+
await Database.DisposeAsync();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 2. Define the collection
|
|
195
|
+
[CollectionDefinition("Database")]
|
|
196
|
+
public class DatabaseCollection : ICollectionFixture<DatabaseFixture> { }
|
|
197
|
+
|
|
198
|
+
// 3. Use in test classes
|
|
199
|
+
[Collection("Database")]
|
|
200
|
+
public class OrderRepositoryTests
|
|
201
|
+
{
|
|
202
|
+
private readonly DatabaseFixture _fixture;
|
|
203
|
+
|
|
204
|
+
public OrderRepositoryTests(DatabaseFixture fixture)
|
|
205
|
+
{
|
|
206
|
+
_fixture = fixture;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
[Fact]
|
|
210
|
+
public async Task GetById_ReturnsOrder()
|
|
211
|
+
{
|
|
212
|
+
// Use _fixture.Database
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Parameterized Test Template
|
|
218
|
+
|
|
219
|
+
```csharp
|
|
220
|
+
public class CalculatorTests
|
|
221
|
+
{
|
|
222
|
+
private readonly Calculator _sut = new();
|
|
223
|
+
|
|
224
|
+
// Simple inline data
|
|
225
|
+
[Theory]
|
|
226
|
+
[InlineData(2, 3, 5)]
|
|
227
|
+
[InlineData(-1, 1, 0)]
|
|
228
|
+
[InlineData(0, 0, 0)]
|
|
229
|
+
public void Add_WithValidInputs_ReturnsCorrectSum(int a, int b, int expected)
|
|
230
|
+
{
|
|
231
|
+
var result = _sut.Add(a, b);
|
|
232
|
+
Assert.Equal(expected, result);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Complex data from method
|
|
236
|
+
[Theory]
|
|
237
|
+
[MemberData(nameof(GetDivisionTestCases))]
|
|
238
|
+
public void Divide_WithValidInputs_ReturnsCorrectResult(
|
|
239
|
+
decimal numerator, decimal denominator, decimal expected, string scenario)
|
|
240
|
+
{
|
|
241
|
+
var result = _sut.Divide(numerator, denominator);
|
|
242
|
+
Assert.Equal(expected, result);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public static IEnumerable<object[]> GetDivisionTestCases()
|
|
246
|
+
{
|
|
247
|
+
yield return new object[] { 10m, 2m, 5m, "Simple division" };
|
|
248
|
+
yield return new object[] { 7m, 2m, 3.5m, "Division with decimal result" };
|
|
249
|
+
yield return new object[] { -10m, 2m, -5m, "Negative numerator" };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Logger Testing Template
|
|
255
|
+
|
|
256
|
+
```csharp
|
|
257
|
+
public class ServiceWithLoggingTests
|
|
258
|
+
{
|
|
259
|
+
private readonly FakeLogger<MyService> _fakeLogger;
|
|
260
|
+
private readonly MyService _sut;
|
|
261
|
+
|
|
262
|
+
public ServiceWithLoggingTests()
|
|
263
|
+
{
|
|
264
|
+
_fakeLogger = new FakeLogger<MyService>();
|
|
265
|
+
_sut = new MyService(_fakeLogger);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
[Fact]
|
|
269
|
+
public async Task ProcessOrder_LogsOrderId()
|
|
270
|
+
{
|
|
271
|
+
// Arrange
|
|
272
|
+
var orderId = 123;
|
|
273
|
+
|
|
274
|
+
// Act
|
|
275
|
+
await _sut.ProcessOrderAsync(orderId);
|
|
276
|
+
|
|
277
|
+
// Assert - Verify structured log properties
|
|
278
|
+
var logEntry = _fakeLogger.Collector.GetSnapshot()
|
|
279
|
+
.Single(r => r.Level == LogLevel.Information);
|
|
280
|
+
|
|
281
|
+
Assert.NotNull(logEntry.StructuredState);
|
|
282
|
+
var state = logEntry.StructuredState!.ToDictionary(x => x.Key, x => x.Value);
|
|
283
|
+
|
|
284
|
+
Assert.True(state.ContainsKey("OrderId"));
|
|
285
|
+
Assert.Equal("123", state["OrderId"]);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
[Fact]
|
|
289
|
+
public async Task ProcessOrder_WhenFails_LogsError()
|
|
290
|
+
{
|
|
291
|
+
// Arrange - setup to cause failure
|
|
292
|
+
|
|
293
|
+
// Act
|
|
294
|
+
await Assert.ThrowsAsync<Exception>(() => _sut.ProcessOrderAsync(-1));
|
|
295
|
+
|
|
296
|
+
// Assert
|
|
297
|
+
var errorLog = _fakeLogger.Collector.GetSnapshot()
|
|
298
|
+
.SingleOrDefault(r => r.Level == LogLevel.Error);
|
|
299
|
+
|
|
300
|
+
Assert.NotNull(errorLog);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|