Skip to content

Commit 5e41ebe

Browse files
committed
feat: Introduce PartialDate type with JSON serialization
1 parent 1ef8773 commit 5e41ebe

12 files changed

Lines changed: 470 additions & 98 deletions

File tree

docs/getting-started.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,9 @@ dotnet run --project src/BookStore.Tests/BookStore.Tests.csproj
285285

286286
All tests use TUnit's fluent assertion syntax:
287287
```csharp
288-
await Assert.That(result).IsNotNull();
289-
await Assert.That(actual).IsEqualTo(expected);
290-
await Assert.That(collection).Contains(item);
288+
_ = await Assert.That(result).IsNotNull();
289+
_ = await Assert.That(actual).IsEqualTo(expected);
290+
_ = await Assert.That(collection).Contains(item);
291291
```
292292

293293
> [!NOTE]

docs/testing-guide.md

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public class BookHandlerTests
6767
var result = BookHandlers.Handle(command, session);
6868

6969
// Assert
70-
await Assert.That(result).IsNotNull();
70+
_ = await Assert.That(result).IsNotNull();
7171
session.Events.Received(1).StartStream<BookAggregate>(...);
7272
}
7373
}
@@ -81,50 +81,50 @@ TUnit uses a fluent, async-first assertion syntax:
8181

8282
```csharp
8383
// Equality
84-
await Assert.That(actual).IsEqualTo(expected);
85-
await Assert.That(actual).IsNotEqualTo(unexpected);
84+
_ = await Assert.That(actual).IsEqualTo(expected);
85+
_ = await Assert.That(actual).IsNotEqualTo(unexpected);
8686

8787
// Null checks
88-
await Assert.That(value).IsNotNull();
89-
await Assert.That(value).IsNull();
88+
_ = await Assert.That(value).IsNotNull();
89+
_ = await Assert.That(value).IsNull();
9090

9191
// Boolean
92-
await Assert.That(condition).IsTrue();
93-
await Assert.That(condition).IsFalse();
92+
_ = await Assert.That(condition).IsTrue();
93+
_ = await Assert.That(condition).IsFalse();
9494

9595
// Type checks
96-
await Assert.That(result).IsTypeOf<ExpectedType>();
97-
await Assert.That(result).IsNotTypeOf<UnexpectedType>();
96+
_ = await Assert.That(result).IsTypeOf<ExpectedType>();
97+
_ = await Assert.That(result).IsNotTypeOf<UnexpectedType>();
9898
```
9999

100100
### Collection Assertions
101101

102102
```csharp
103103
// Contains
104-
await Assert.That(collection).Contains(item);
105-
await Assert.That(collection).DoesNotContain(item);
104+
_ = await Assert.That(collection).Contains(item);
105+
_ = await Assert.That(collection).DoesNotContain(item);
106106

107107
// Empty/Not Empty
108-
await Assert.That(collection).IsEmpty();
109-
await Assert.That(collection).IsNotEmpty();
108+
_ = await Assert.That(collection).IsEmpty();
109+
_ = await Assert.That(collection).IsNotEmpty();
110110

111111
// Count
112-
await Assert.That(collection).Count().IsEqualTo(3);
112+
_ = await Assert.That(collection).Count().IsEqualTo(3);
113113
```
114114

115115
### String Assertions
116116

117117
```csharp
118118
// Contains
119-
await Assert.That(text).Contains("substring");
120-
await Assert.That(text).DoesNotContain("missing");
119+
_ = await Assert.That(text).Contains("substring");
120+
_ = await Assert.That(text).DoesNotContain("missing");
121121

122122
// Starts/Ends With
123-
await Assert.That(text).StartsWith("prefix");
124-
await Assert.That(text).EndsWith("suffix");
123+
_ = await Assert.That(text).StartsWith("prefix");
124+
_ = await Assert.That(text).EndsWith("suffix");
125125

126126
// Regex
127-
await Assert.That(text).Matches(@"pattern");
127+
_ = await Assert.That(text).Matches(@"pattern");
128128
```
129129

130130
### Exception Assertions
@@ -163,7 +163,7 @@ public async Task UpdateBookHandler_WithMissingBook_ShouldReturnNotFound()
163163
var result = await BookHandlers.Handle(command, session, context);
164164

165165
// Assert
166-
await Assert.That(result).IsTypeOf<NotFound>();
166+
_ = await Assert.That(result).IsTypeOf<NotFound>();
167167
}
168168
```
169169

@@ -180,7 +180,7 @@ public async Task DateTimeOffset_Should_Serialize_As_ISO8601_With_UTC()
180180
var testObject = new { Timestamp = new DateTimeOffset(2025, 12, 26, 17, 16, 9, 123, TimeSpan.Zero) };
181181
var json = JsonSerializer.Serialize(testObject, _options);
182182

183-
await Assert.That(json).Contains("\"timestamp\":\"2025-12-26T17:16:09.123+00:00\"");
183+
_ = await Assert.That(json).Contains("\"timestamp\":\"2025-12-26T17:16:09.123+00:00\"");
184184
}
185185
```
186186

@@ -203,7 +203,7 @@ public async Task GetWebResourceRootReturnsOkStatusCode(CancellationToken cancel
203203
var httpClient = app.CreateHttpClient("webfrontend");
204204
var response = await httpClient.GetAsync("/", cancellationToken);
205205

206-
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
206+
_ = await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
207207
}
208208
```
209209

@@ -378,7 +378,7 @@ All TUnit assertions are async, so tests should be `async Task`:
378378
[Test]
379379
public async Task MyTest() // ✓ Correct
380380
{
381-
await Assert.That(result).IsNotNull();
381+
_ = await Assert.That(result).IsNotNull();
382382
}
383383
384384
[Test]
@@ -394,7 +394,7 @@ TUnit's fluent syntax is more readable:
394394

395395
```csharp
396396
// ✓ TUnit style
397-
await Assert.That(result).IsEqualTo(expected);
397+
_ = await Assert.That(result).IsEqualTo(expected);
398398
399399
// ✗ Old xUnit style (don't use)
400400
Assert.Equal(expected, result);
@@ -442,7 +442,7 @@ public async Task Updates_configuration_file()
442442
{
443443
await ConfigurationManager.SetAsync("key", "value");
444444
var result = await ConfigurationManager.GetAsync("key");
445-
await Assert.That(result).IsEqualTo("value");
445+
_ = await Assert.That(result).IsEqualTo("value");
446446
}
447447
```
448448

@@ -547,7 +547,7 @@ public class BadTests
547547
public async Task Test1()
548548
{
549549
counter++;
550-
await Assert.That(counter).IsEqualTo(1); // May fail if tests run in parallel
550+
_ = await Assert.That(counter).IsEqualTo(1); // May fail if tests run in parallel
551551
}
552552
}
553553
@@ -559,7 +559,7 @@ public class GoodTests
559559
{
560560
var counter = 0; // Local state
561561
counter++;
562-
await Assert.That(counter).IsEqualTo(1);
562+
_ = await Assert.That(counter).IsEqualTo(1);
563563
}
564564
}
565565
```
@@ -582,7 +582,7 @@ public async Task CreateBook_PersistsBookToDatabase()
582582
{
583583
var book = await bookService.CreateBook(...);
584584
var retrieved = await bookService.GetBook(book.Id);
585-
await Assert.That(retrieved).IsNotNull();
585+
_ = await Assert.That(retrieved).IsNotNull();
586586
}
587587
```
588588

@@ -789,13 +789,13 @@ public void StoreResult()
789789

790790
```csharp
791791
// ✗ Bad: Expensive operation in assertion
792-
await Assert.That(await GetAllUsersFromDatabase())
792+
_ = await Assert.That(await GetAllUsersFromDatabase())
793793
.Count()
794794
.IsEqualTo(1000);
795795
796796
// ✓ Good: Use efficient queries
797797
var userCount = await GetUserCountFromDatabase();
798-
await Assert.That(userCount).IsEqualTo(1000);
798+
_ = await Assert.That(userCount).IsEqualTo(1000);
799799
```
800800

801801
### CI/CD Performance
@@ -853,10 +853,10 @@ If you're familiar with xUnit, here are the key differences:
853853
| `[Fact]` | `[Test]` |
854854
| `[Theory]` | `[Test]` |
855855
| `[InlineData(...)]` | `[Arguments(...)]` |
856-
| `Assert.Equal(expected, actual)` | `await Assert.That(actual).IsEqualTo(expected)` |
857-
| `Assert.NotNull(value)` | `await Assert.That(value).IsNotNull()` |
858-
| `Assert.True(condition)` | `await Assert.That(condition).IsTrue()` |
859-
| `Assert.Contains(item, collection)` | `await Assert.That(collection).Contains(item)` |
856+
| `Assert.Equal(expected, actual)` | `_ = await Assert.That(actual).IsEqualTo(expected)` |
857+
| `Assert.NotNull(value)` | `_ = await Assert.That(value).IsNotNull()` |
858+
| `Assert.True(condition)` | `_ = await Assert.That(condition).IsTrue()` |
859+
| `Assert.Contains(item, collection)` | `_ = await Assert.That(collection).Contains(item)` |
860860

861861
See the [TUnit migration guide](https://tunit.dev/docs/migration/xunit/) for more details.
862862

src/ApiService/BookStore.ApiService.Tests/ConfigurationValidationTests.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public async Task PaginationOptions_DefaultPageSizeNegative_FailsValidation()
4141
var results = ValidateModel(options);
4242

4343
// Assert
44-
using var scope = Assert.Multiple();
44+
using var _ = Assert.Multiple();
4545
_ = await Assert.That(results).Count().IsEqualTo(1);
4646
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize must be between 1 and 1000");
4747
}
@@ -61,7 +61,7 @@ public async Task PaginationOptions_DefaultPageSizeZero_FailsValidation()
6161
var results = ValidateModel(options);
6262

6363
// Assert
64-
using var scope = Assert.Multiple();
64+
using var _ = Assert.Multiple();
6565
_ = await Assert.That(results).Count().IsEqualTo(1);
6666
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize must be between 1 and 1000");
6767
}
@@ -81,7 +81,7 @@ public async Task PaginationOptions_DefaultPageSizeTooLarge_FailsValidation()
8181
var results = ValidateModel(options);
8282

8383
// Assert
84-
using var scope = Assert.Multiple();
84+
using var _ = Assert.Multiple();
8585
_ = await Assert.That(results).Count().IsGreaterThanOrEqualTo(1);
8686
_ = await Assert.That(results.Any(r => r.ErrorMessage!.Contains("DefaultPageSize must be between 1 and 1000"))).IsTrue();
8787
}
@@ -101,7 +101,7 @@ public async Task PaginationOptions_MaxPageSizeNegative_FailsValidation()
101101
var results = ValidateModel(options);
102102

103103
// Assert
104-
using var scope = Assert.Multiple();
104+
using var _ = Assert.Multiple();
105105
_ = await Assert.That(results).Count().IsEqualTo(1);
106106
_ = await Assert.That(results[0].ErrorMessage).Contains("MaxPageSize must be between 1 and 1000");
107107
}
@@ -121,7 +121,7 @@ public async Task PaginationOptions_MaxPageSizeZero_FailsValidation()
121121
var results = ValidateModel(options);
122122

123123
// Assert
124-
using var scope = Assert.Multiple();
124+
using var _ = Assert.Multiple();
125125
_ = await Assert.That(results).Count().IsEqualTo(1);
126126
_ = await Assert.That(results[0].ErrorMessage).Contains("MaxPageSize must be between 1 and 1000");
127127
}
@@ -141,7 +141,7 @@ public async Task PaginationOptions_DefaultPageSizeGreaterThanMaxPageSize_FailsV
141141
var results = ValidateModel(options);
142142

143143
// Assert
144-
using var scope = Assert.Multiple();
144+
using var _ = Assert.Multiple();
145145
_ = await Assert.That(results).Count().IsEqualTo(1);
146146
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize (100) cannot be greater than MaxPageSize (50)");
147147
}
@@ -179,7 +179,7 @@ public async Task LocalizationOptions_DefaultCultureEmpty_FailsValidation()
179179
var results = ValidateModel(options);
180180

181181
// Assert
182-
using var scope = Assert.Multiple();
182+
using var _ = Assert.Multiple();
183183
_ = await Assert.That(results).IsNotEmpty();
184184
_ = await Assert.That(results.Any(r => r.ErrorMessage!.Contains("DefaultCulture"))).IsTrue();
185185
}
@@ -199,7 +199,7 @@ public async Task LocalizationOptions_SupportedCulturesEmpty_FailsValidation()
199199
var results = ValidateModel(options);
200200

201201
// Assert
202-
using var scope = Assert.Multiple();
202+
using var _ = Assert.Multiple();
203203
_ = await Assert.That(results).IsNotEmpty();
204204
_ = await Assert.That(results.Any(r => r.ErrorMessage!.Contains("At least one supported culture"))).IsTrue();
205205
}
@@ -219,7 +219,7 @@ public async Task LocalizationOptions_DefaultCultureNotInSupportedCultures_Fails
219219
var results = ValidateModel(options);
220220

221221
// Assert
222-
using var scope = Assert.Multiple();
222+
using var _ = Assert.Multiple();
223223
_ = await Assert.That(results).Count().IsEqualTo(1);
224224
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultCulture 'en-US' must be included in SupportedCultures");
225225
}

src/ApiService/BookStore.ApiService.Tests/CultureCacheTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public async Task IsValidCultureCode_CaseInsensitive_ReturnsTrue()
8080
var result2 = Infrastructure.CultureCache.IsValidCultureCode("PT");
8181

8282
// Assert
83-
using var scope = Assert.Multiple();
83+
using var _ = Assert.Multiple();
8484
_ = await Assert.That(result1).IsTrue();
8585
_ = await Assert.That(result2).IsTrue();
8686
}
@@ -143,7 +143,7 @@ public async Task GetInvalidCodes_SomeInvalid_ReturnsInvalidOnes()
143143
var invalidCodes = Infrastructure.CultureCache.GetInvalidCodes(codes);
144144

145145
// Assert
146-
using var scope = Assert.Multiple();
146+
using var _ = Assert.Multiple();
147147
_ = await Assert.That(invalidCodes).Count().IsEqualTo(2);
148148
_ = await Assert.That(invalidCodes).Contains("xx-XX");
149149
_ = await Assert.That(invalidCodes).Contains("invalid");

src/ApiService/BookStore.ApiService.Tests/Handlers/BookHandlerTests.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using BookStore.ApiService.Commands;
33
using BookStore.ApiService.Events;
44
using BookStore.ApiService.Handlers.Books;
5+
using BookStore.ApiService.Models;
56
using Marten;
67
using Microsoft.AspNetCore.Http;
78
using NSubstitute;
@@ -22,11 +23,13 @@ public async Task CreateBookHandler_ShouldStartStreamWithBookAddedEvent()
2223
var command = new CreateBook(
2324
"Clean Code",
2425
"978-0132350884",
25-
"A Handbook of Agile Software Craftsmanship",
26-
new DateOnly(2008, 8, 1),
27-
Guid.CreateVersion7(),
28-
[Guid.CreateVersion7()],
29-
[Guid.CreateVersion7()]);
26+
"en",
27+
[], // Translations
28+
new PartialDate(2008, 8, 1),
29+
Guid.CreateVersion7(), // PublisherId
30+
[Guid.CreateVersion7()], // AuthorIds
31+
[Guid.CreateVersion7()] // CategoryIds
32+
);
3033

3134
var session = Substitute.For<IDocumentSession>();
3235
_ = session.CorrelationId.Returns("test-correlation-id");
@@ -52,11 +55,13 @@ public async Task UpdateBookHandler_WithMissingBook_ShouldReturnNotFound()
5255
Guid.CreateVersion7(),
5356
"Updated Title",
5457
null,
55-
null,
56-
null,
57-
null,
58-
[],
59-
[]);
58+
"en", // Language (non-nullable)
59+
null, // Translations
60+
null, // PartialDate
61+
null, // PublisherId
62+
[], // AuthorIds
63+
[] // CategoryIds
64+
);
6065

6166
var session = Substitute.For<IDocumentSession>();
6267
var context = new DefaultHttpContext();

0 commit comments

Comments
 (0)