{"id":"c4237937-6d93-4efb-a6d6-60466330d8d3","shortId":"488CXg","kind":"skill","title":"dotnet-testing-fluentvalidation-testing","tagline":"測試 FluentValidation 驗證器的專門技能。當需要為 Validator 類別建立測試、驗證業務規則、測試錯誤訊息時使用。涵蓋 FluentValidation.TestHelper 完整使用、ShouldHaveValidationErrorFor、非同步驗證、跨欄位邏輯等。\nMake sure to use this skill whenever the user mentions FluentValidation testing, Validator testing, ShouldHaveValidationErrorFor, Tes","description":"# FluentValidation 驗證器測試指南\n\n## 為什麼要測試驗證器？\n\n驗證器是應用程式的第一道防線，測試驗證器能：\n\n1. **確保資料完整性** - 防止無效資料進入系統\n2. **業務規則文件化** - 測試即活文件，清楚展示業務規則\n3. **安全性保障** - 防止惡意或不當資料輸入\n4. **重構安全網** - 業務規則變更時提供保障\n5. **跨欄位邏輯驗證** - 確保複雜邏輯正確運作\n\n## 前置需求\n\n### 套件安裝\n\n```xml\n<PackageReference Include=\"FluentValidation\" Version=\"12.1.1\" />\n<PackageReference Include=\"xunit\" Version=\"2.9.3\" />\n<PackageReference Include=\"Microsoft.Extensions.TimeProvider.Testing\" Version=\"10.4.0\" />\n<PackageReference Include=\"NSubstitute\" Version=\"5.3.0\" />\n<PackageReference Include=\"AwesomeAssertions\" Version=\"9.4.0\" />\n```\n\n> **注意**：`FluentValidation.TestHelper` 命名空間（`TestValidate`、`ShouldHaveValidationErrorFor` 等 API）已包含在 `FluentValidation` 主套件中，不需要額外安裝獨立套件。只需 `using FluentValidation.TestHelper;` 即可使用。\n\n> **FluentValidation 12.x 注意事項**：FluentValidation 12.0 為主要版本升級，最低需求為 **.NET 8**。已移除的 API 包括 `Transform`/`TransformForEach`（改用 `Must` + 手動轉換）、`InjectValidator`（改用建構子注入 + `SetValidator`）、`CascadeMode.StopOnFirstFailure`（改用 `RuleLevelCascadeMode = CascadeMode.Stop`）。`ShouldHaveAnyValidationError` 已更名為 `ShouldHaveValidationErrors`。完整遷移指南請參閱 [FluentValidation 12.0 Upgrade Guide](https://docs.fluentvalidation.net/en/latest/upgrading-to-12.html)。\n\n### 基本 using 指令\n\n```csharp\nusing FluentValidation;\nusing FluentValidation.TestHelper;\nusing Microsoft.Extensions.Time.Testing;\nusing NSubstitute;\nusing Xunit;\nusing AwesomeAssertions;\n```\n\n## 核心測試模式\n\n本節涵蓋 7 種核心測試模式，每種模式包含驗證器定義與完整測試範例。\n\n> 完整程式碼範例請參考 [references/core-test-patterns.md](references/core-test-patterns.md)\n\n- **模式 1：基本欄位驗證** — 使用 `TestValidate` + `ShouldHaveValidationErrorFor` / `ShouldNotHaveValidationErrorFor` 測試單一欄位規則\n- **模式 2：參數化測試** — 使用 `[Theory]` + `[InlineData]` 測試多種無效/有效輸入組合\n- **模式 3：跨欄位驗證** — 密碼確認、自訂 `Must()` 規則等多欄位關聯驗證\n- **模式 4：時間相依驗證** — 注入 `TimeProvider`，搭配 `FakeTimeProvider` 控制時間進行測試\n- **模式 5：條件式驗證** — 使用 `.When()` 的可選欄位驗證，測試條件觸發與跳過情境\n- **模式 6：非同步驗證** — `MustAsync` + `TestValidateAsync`，搭配 NSubstitute Mock 外部服務\n- **模式 7：集合驗證** — 驗證集合非空與元素有效性\n\n### 快速範例：基本欄位驗證\n\n```csharp\npublic class UserValidatorTests\n{\n    private readonly UserValidator _validator = new();\n\n    [Fact]\n    public void Validate_空白使用者名稱_應該驗證失敗()\n    {\n        var result = _validator.TestValidate(\n            new UserRegistrationRequest { Username = \"\" });\n\n        result.ShouldHaveValidationErrorFor(x => x.Username)\n              .WithErrorMessage(\"使用者名稱不可為 null 或空白\");\n    }\n}\n```\n\n## FluentValidation.TestHelper 核心 API\n\n### 測試方法\n\n| 方法                       | 用途           | 範例                                          |\n| -------------------------- | -------------- | --------------------------------------------- |\n| `TestValidate(model)`      | 執行同步驗證   | `_validator.TestValidate(request)`            |\n| `TestValidateAsync(model)` | 執行非同步驗證 | `await _validator.TestValidateAsync(request)` |\n\n### 斷言方法\n\n| 方法                                               | 用途                     | 範例                                                   |\n| -------------------------------------------------- | ------------------------ | ------------------------------------------------------ |\n| `ShouldHaveValidationErrorFor(x => x.Property)`    | 斷言該屬性應該有錯誤     | `result.ShouldHaveValidationErrorFor(x => x.Username)` |\n| `ShouldNotHaveValidationErrorFor(x => x.Property)` | 斷言該屬性不應該有錯誤   | `result.ShouldNotHaveValidationErrorFor(x => x.Email)` |\n| `ShouldNotHaveAnyValidationErrors()`               | 斷言整個物件沒有任何錯誤 | `result.ShouldNotHaveAnyValidationErrors()`            |\n\n### 錯誤訊息驗證\n\n| 方法                       | 用途             | 範例                                      |\n| -------------------------- | ---------------- | ----------------------------------------- |\n| `WithErrorMessage(string)` | 驗證錯誤訊息內容 | `.WithErrorMessage(\"使用者名稱不可為空\")` |\n| `WithErrorCode(string)`    | 驗證錯誤代碼     | `.WithErrorCode(\"NOT_EMPTY\")`             |\n\n## 測試最佳實踐\n\n### 推薦做法\n\n1. **使用參數化測試** - 用 Theory 測試多種輸入組合\n2. **測試邊界值** - 特別注意邊界條件\n3. **控制時間** - 使用 FakeTimeProvider 處理時間相依\n4. **Mock 外部依賴** - 使用 NSubstitute 隔離外部服務\n5. **建立輔助方法** - 統一管理測試資料\n6. **清楚的測試命名** - 使用 `方法_情境_預期結果` 格式\n7. **測試錯誤訊息** - 確保使用者看到正確的錯誤訊息\n\n### 避免做法\n\n1. **避免使用 DateTime.Now** - 會導致測試不穩定\n2. **避免測試過度耦合** - 每個測試只驗證一個規則\n3. **避免硬編碼測試資料** - 使用輔助方法建立\n4. **避免忽略邊界條件** - 邊界值是最容易出錯的地方\n5. **避免跳過錯誤訊息驗證** - 錯誤訊息是使用者體驗的一部分\n\n## 常見測試場景\n\n### 場景 1：Email 格式驗證\n\n```csharp\n[Theory]\n[InlineData(\"\", \"電子郵件不可為 null 或空白\")]\n[InlineData(\"invalid\", \"電子郵件格式不正確\")]\n[InlineData(\"@example.com\", \"電子郵件格式不正確\")]\npublic void Validate_無效Email_應該驗證失敗(string email, string expectedError)\n{\n    var request = new UserRegistrationRequest { Email = email };\n    var result = _validator.TestValidate(request);\n    result.ShouldHaveValidationErrorFor(x => x.Email).WithErrorMessage(expectedError);\n}\n```\n\n### 場景 2：年齡範圍驗證\n\n```csharp\n[Theory]\n[InlineData(17, \"年齡必須大於或等於 18 歲\")]\n[InlineData(121, \"年齡必須小於或等於 120 歲\")]\npublic void Validate_無效年齡_應該驗證失敗(int age, string expectedError)\n{\n    var request = new UserRegistrationRequest { Age = age };\n    var result = _validator.TestValidate(request);\n    result.ShouldHaveValidationErrorFor(x => x.Age).WithErrorMessage(expectedError);\n}\n```\n\n### 場景 3：必填欄位驗證\n\n```csharp\n[Fact]\npublic void Validate_未同意條款_應該驗證失敗()\n{\n    var request = new UserRegistrationRequest { AgreeToTerms = false };\n    var result = _validator.TestValidate(request);\n    result.ShouldHaveValidationErrorFor(x => x.AgreeToTerms)\n          .WithErrorMessage(\"必須同意使用條款\");\n}\n```\n\n## 測試輔助工具\n\n### 測試資料建構器\n\n```csharp\npublic static class TestDataBuilder\n{\n    public static UserRegistrationRequest CreateValidRequest()\n    {\n        return new UserRegistrationRequest\n        {\n            Username = \"testuser123\",\n            Email = \"test@example.com\",\n            Password = \"TestPass123\",\n            ConfirmPassword = \"TestPass123\",\n            BirthDate = new DateTime(1990, 1, 1),\n            Age = 34,\n            PhoneNumber = \"0912345678\",\n            Roles = new List<string> { \"User\" },\n            AgreeToTerms = true\n        };\n    }\n\n    public static UserRegistrationRequest WithUsername(this UserRegistrationRequest request, string username)\n    {\n        request.Username = username;\n        return request;\n    }\n\n    public static UserRegistrationRequest WithEmail(this UserRegistrationRequest request, string email)\n    {\n        request.Email = email;\n        return request;\n    }\n}\n\n// 使用範例\nvar request = TestDataBuilder.CreateValidRequest()\n                            .WithUsername(\"newuser\")\n                            .WithEmail(\"new@example.com\");\n```\n\n## 與其他技能整合\n\n此技能可與以下技能組合使用：\n\n- **unit-test-fundamentals**: 單元測試基礎與 3A 模式\n- **test-naming-conventions**: 測試命名規範\n- **nsubstitute-mocking**: Mock 外部服務依賴\n- **test-data-builder-pattern**: 建構複雜測試資料\n- **datetime-testing-timeprovider**: 時間相依測試\n\n## 疑難排解\n\n### Q1: 如何測試需要資料庫查詢的驗證？\n\n**A:** 使用 Mock 隔離資料庫依賴：\n\n```csharp\n_mockUserService.IsUsernameAvailableAsync(\"username\")\n                .Returns(Task.FromResult(false));\n```\n\n### Q2: 如何處理時間相關的驗證？\n\n**A:** 使用 FakeTimeProvider 控制時間：\n\n```csharp\n_fakeTimeProvider.SetUtcNow(new DateTime(2024, 1, 1));\n```\n\n### Q3: 如何測試複雜的跨欄位驗證？\n\n**A:** 分別測試每個條件，確保完整覆蓋：\n\n```csharp\n// 測試生日已過的情況\n// 測試生日未到的情況\n// 測試邊界日期\n```\n\n### Q4: 應該測試到什麼程度？\n\n**A:** 重點測試：\n\n- 每個驗證規則至少一個測試\n- 邊界值和特殊情況\n- 錯誤訊息正確性\n- 跨欄位邏輯的所有組合\n\n## 範本檔案參考\n\n本技能提供以下範本檔案：\n\n- `templates/validator-test-template.cs`: 完整的驗證器測試範例\n- `templates/async-validator-examples.cs`: 非同步驗證範例\n\n## 輸出格式\n\n- 產生 Validator 測試類別（含 TestHelper 設定）\n- 使用 ShouldHaveValidationErrorFor/ShouldNotHaveValidationErrorFor 斷言\n- 包含非同步驗證、跨欄位邏輯測試範例\n- 提供 .csproj 套件參考（FluentValidation — 已包含 TestHelper API）\n\n## 參考資源\n\n### 原始文章\n\n本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章：\n\n- **Day 18 - 驗證測試：FluentValidation Test Extensions**\n  - 鐵人賽文章：https://ithelp.ithome.com.tw/articles/10376147\n  - 範例程式碼：https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day18\n\n### 官方文件\n\n- [FluentValidation Documentation](https://docs.fluentvalidation.net/)\n- [FluentValidation.TestHelper](https://docs.fluentvalidation.net/en/latest/testing.html)\n- [FluentValidation GitHub](https://github.com/FluentValidation/FluentValidation)\n\n### 相關技能\n\n- `unit-test-fundamentals` - 單元測試基礎\n- `nsubstitute-mocking` - 測試替身與模擬","tags":["dotnet","testing","fluentvalidation","agent","skills","kevintsengtw","agent-skills","ai-assisted-development","copilot-skills","csharp","dotnet-testing","github-copilot"],"capabilities":["skill","source-kevintsengtw","skill-dotnet-testing-fluentvalidation-testing","topic-agent-skills","topic-ai-assisted-development","topic-copilot-skills","topic-csharp","topic-dotnet","topic-dotnet-testing","topic-github-copilot","topic-integration-testing","topic-testing","topic-unit-testing","topic-xunit"],"categories":["dotnet-testing-agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-fluentvalidation-testing","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add kevintsengtw/dotnet-testing-agent-skills","source_repo":"https://github.com/kevintsengtw/dotnet-testing-agent-skills","install_from":"skills.sh"}},"qualityScore":"0.461","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 23 github stars · SKILL.md body (8,123 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T13:02:27.004Z","embedding":null,"createdAt":"2026-04-18T23:04:31.032Z","updatedAt":"2026-04-24T13:02:27.004Z","lastSeenAt":"2026-04-24T13:02:27.004Z","tsv":"'/)':622 '/articles/10376147':612 '/en/latest/testing.html)':626 '/en/latest/upgrading-to-12.html)':110 '/fluentvalidation/fluentvalidation)':631 '/kevintsengtw/30days_in_testing_samples/tree/main/day18':616 '0912345678':457 '1':41,136,272,305,323,452,453,552,553 '12':76 '12.0':80,105 '120':375 '121':373 '17':368 '18':370,604 '1990':451 '2':44,144,277,309,363 '2024':551 '3':48,152,280,312,402 '30':600 '34':455 '3a':505 '4':51,159,285,315 '5':54,167,291,318 '6':174,294 '7':129,183,301 '8':84 'age':383,390,391,454 'agreetoterm':415,462 'api':66,86,218,595 'await':231 'awesomeassert':126 'birthdat':448 'builder':520 'cascademode.stop':99 'cascademode.stoponfirstfailure':96 'class':190,431 'confirmpassword':446 'convent':510 'createvalidrequest':436 'csharp':114,188,326,365,404,428,535,547,559 'csproj':590 'data':519 'datetim':450,524,550 'datetime-testing-timeprovid':523 'datetime.now':307 'day':603 'docs.fluentvalidation.net':109,621,625 'docs.fluentvalidation.net/)':620 'docs.fluentvalidation.net/en/latest/testing.html)':624 'docs.fluentvalidation.net/en/latest/upgrading-to-12.html)':108 'document':619 'dotnet':2 'dotnet-testing-fluentvalidation-test':1 'email':324,344,351,352,442,485,487 'empti':269 'example.com':336 'expectederror':346,361,385,400 'extens':608 'fact':197,405 'faketimeprovid':164,283,545 'faketimeprovider.setutcnow':548 'fals':416,540 'fluentvalid':4,7,30,36,68,75,79,104,116,592,606,618,627 'fluentvalidation.testhelper':15,61,73,118,216,623 'fundament':503,636 'github':628 'github.com':615,630 'github.com/fluentvalidation/fluentvalidation)':629 'github.com/kevintsengtw/30days_in_testing_samples/tree/main/day18':614 'guid':107 'injectvalid':93 'inlinedata':148,328,332,335,367,372 'int':382 'invalid':333 'ithelp.ithome.com.tw':611 'ithelp.ithome.com.tw/articles/10376147':610 'list':460 'make':20 'mention':29 'microsoft.extensions.time.testing':120 'mock':180,286,514,515,533,640 'mockuserservice.isusernameavailableasync':536 'model':224,229 'must':91,156 'mustasync':176 'name':509 'net':83 'new':196,206,349,388,413,438,449,459,549 'new@example.com':497 'newus':495 'nsubstitut':122,179,289,513,639 'nsubstitute-mock':512,638 'null':214,330 'password':444 'pattern':521 'phonenumb':456 'privat':192 'public':189,198,338,377,406,429,433,464,477 'q1':529 'q2':541 'q3':554 'q4':563 'readon':193 'references/core-test-patterns.md':133,134 'request':227,233,348,356,387,395,412,420,470,476,483,489,492 'request.email':486 'request.username':473 'result':204,354,393,418 'result.shouldhavevalidationerrorfor':209,242,357,396,421 'result.shouldnothaveanyvalidationerrors':254 'result.shouldnothavevalidationerrorfor':249 'return':437,475,488,538 'role':458 'rulelevelcascademod':98 'setvalid':95 'shouldhaveanyvalidationerror':100 'shouldhavevalidationerror':102 'shouldhavevalidationerrorfor':17,34,64,140,238 'shouldhavevalidationerrorfor/shouldnothavevalidationerrorfor':585 'shouldnothaveanyvalidationerror':252 'shouldnothavevalidationerrorfor':141,245 'skill':25 'skill-dotnet-testing-fluentvalidation-testing' 'source-kevintsengtw' 'static':430,434,465,478 'string':260,265,343,345,384,471,484 'sure':21 'task.fromresult':539 'templates/async-validator-examples.cs':575 'templates/validator-test-template.cs':573 'tes':35 'test':3,5,31,33,502,508,518,525,607,635 'test-data-builder-pattern':517 'test-naming-convent':507 'test@example.com':443 'testdatabuild':432 'testdatabuilder.createvalidrequest':493 'testhelp':582,594 'testpass123':445,447 'testuser123':441 'testvalid':63,139,223 'testvalidateasync':177,228 'theori':147,275,327,366 'timeprovid':162,526 'topic-agent-skills' 'topic-ai-assisted-development' 'topic-copilot-skills' 'topic-csharp' 'topic-dotnet' 'topic-dotnet-testing' 'topic-github-copilot' 'topic-integration-testing' 'topic-testing' 'topic-unit-testing' 'topic-xunit' 'transform':88 'transformforeach':89 'true':463 'unit':501,634 'unit-test-fundament':500,633 'upgrad':106 'use':23,72,112,115,117,119,121,123,125 'user':28,461 'usernam':208,440,472,474,537 'userregistrationrequest':207,350,389,414,435,439,466,469,479,482 'uservalid':194 'uservalidatortest':191 'valid':10,32,195,200,340,379,408,579 'validator.testvalidate':205,226,355,394,419 'validator.testvalidateasync':232 'var':203,347,353,386,392,411,417,491 'void':199,339,378,407 'whenev':26 'withemail':480,496 'witherrorcod':264,267 'witherrormessag':212,259,262,360,399,424 'withusernam':467,494 'x':77,210,239,243,246,250,358,397,422 'x.age':398 'x.agreetoterms':423 'x.email':251,359 'x.property':240,247 'x.username':211,244 'xml':59 'xunit':124 '不需要額外安裝獨立套件':70 '主套件中':69 '使用':138,146,169,282,288,296,532,544,584 '使用參數化測試':273 '使用範例':490 '使用者名稱不可為':213 '使用者名稱不可為空':263 '使用輔助方法建立':314 '分別測試每個條件':557 '前置需求':57 '包含非同步驗證':587 '包括':87 '即可使用':74 '原始文章':597 '參數化測試':145 '參考資源':596 '只需':71 '含':581 '命名空間':62 '單元測試基礎':637 '單元測試基礎與':504 '執行同步驗證':225 '執行非同步驗證':230 '基本':111 '基本欄位驗證':137,187 '場景':322,362,401 '外部依賴':287 '外部服務':181 '外部服務依賴':516 '天挑戰':601 '套件參考':591 '套件安裝':58 '如何測試複雜的跨欄位驗證':555 '如何測試需要資料庫查詢的驗證':530 '如何處理時間相關的驗證':542 '安全性保障':49 '完整使用':16 '完整的驗證器測試範例':574 '完整程式碼範例請參考':132 '完整遷移指南請參閱':103 '官方文件':617 '密碼確認':154 '已包含':593 '已包含在':67 '已更名為':101 '已移除的':85 '常見測試場景':321 '年齡必須大於或等於':369 '年齡必須小於或等於':374 '年齡範圍驗證':364 '建構複雜測試資料':522 '建立輔助方法':292 '必填欄位驗證':403 '必須同意使用條款':425 '快速範例':186 '情境':298 '應該測試到什麼程度':564 '應該驗證失敗':202,342,381,410 '或空白':215,331 '手動轉換':92 '指令':113 '控制時間':281,546 '控制時間進行測試':165 '推薦做法':271 '提供':589 '搭配':163,178 '改用':90,97 '改用建構子注入':94 '斷言':586 '斷言整個物件沒有任何錯誤':253 '斷言方法':234 '斷言該屬性不應該有錯誤':248 '斷言該屬性應該有錯誤':241 '方法':220,235,256,297 '時間相依測試':527 '時間相依驗證':160 '最低需求為':82 '會導致測試不穩定':308 '有效輸入組合':150 '未同意條款':409 '本技能內容提煉自':598 '本技能提供以下範本檔案':572 '本節涵蓋':128 '核心':217 '核心測試模式':127 '格式':300 '格式驗證':325 '條件式驗證':168 '業務規則文件化':45 '業務規則變更時提供保障':53 '模式':135,143,151,158,166,173,182,506 '此技能可與以下技能組合使用':499 '歲':371,376 '每個測試只驗證一個規則':311 '每個驗證規則至少一個測試':567 '每種模式包含驗證器定義與完整測試範例':131 '注入':161 '注意':60 '注意事項':78 '涵蓋':14 '清楚展示業務規則':47 '清楚的測試命名':295 '測試':6 '測試即活文件':46 '測試命名規範':511 '測試單一欄位規則':142 '測試多種無效':149 '測試多種輸入組合':276 '測試方法':219 '測試替身與模擬':641 '測試最佳實踐':270 '測試條件觸發與跳過情境':172 '測試生日已過的情況':560 '測試生日未到的情況':561 '測試資料建構器':427 '測試輔助工具':426 '測試邊界值':278 '測試邊界日期':562 '測試錯誤訊息':302 '測試錯誤訊息時使用':13 '測試類別':580 '測試驗證器能':40 '為主要版本升級':81 '為什麼要測試驗證器':38 '無效email':341 '無效年齡':380 '特別注意邊界條件':279 '產生':578 '用':274 '用途':221,236,257 '當需要為':9 '疑難排解':528 '的可選欄位驗證':171 '相關技能':632 '確保使用者看到正確的錯誤訊息':303 '確保完整覆蓋':558 '確保複雜邏輯正確運作':56 '確保資料完整性':42 '種核心測試模式':130 '空白使用者名稱':201 '等':65 '範例':222,237,258 '範例程式碼':613 '範本檔案參考':571 '系列文章':602 '統一管理測試資料':293 '老派軟體工程師的測試修練':599 '自訂':155 '與其他技能整合':498 '處理時間相依':284 '規則等多欄位關聯驗證':157 '設定':583 '跨欄位邏輯測試範例':588 '跨欄位邏輯的所有組合':570 '跨欄位邏輯等':19 '跨欄位邏輯驗證':55 '跨欄位驗證':153 '輸出格式':577 '避免使用':306 '避免做法':304 '避免忽略邊界條件':316 '避免測試過度耦合':310 '避免硬編碼測試資料':313 '避免跳過錯誤訊息驗證':319 '邊界值和特殊情況':568 '邊界值是最容易出錯的地方':317 '重構安全網':52 '重點測試':566 '錯誤訊息是使用者體驗的一部分':320 '錯誤訊息正確性':569 '錯誤訊息驗證':255 '鐵人賽文章':609 '防止惡意或不當資料輸入':50 '防止無效資料進入系統':43 '隔離外部服務':290 '隔離資料庫依賴':534 '集合驗證':184 '電子郵件不可為':329 '電子郵件格式不正確':334,337 '非同步驗證':18,175 '非同步驗證範例':576 '預期結果':299 '類別建立測試':11 '驗證器是應用程式的第一道防線':39 '驗證器測試指南':37 '驗證器的專門技能':8 '驗證業務規則':12 '驗證測試':605 '驗證錯誤代碼':266 '驗證錯誤訊息內容':261 '驗證集合非空與元素有效性':185","prices":[{"id":"812739c3-984f-4a0a-b40f-71796bf19dc2","listingId":"c4237937-6d93-4efb-a6d6-60466330d8d3","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"kevintsengtw","category":"dotnet-testing-agent-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T23:04:31.032Z"}],"sources":[{"listingId":"c4237937-6d93-4efb-a6d6-60466330d8d3","source":"github","sourceId":"kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-fluentvalidation-testing","sourceUrl":"https://github.com/kevintsengtw/dotnet-testing-agent-skills/tree/main/skills/dotnet-testing-fluentvalidation-testing","isPrimary":false,"firstSeenAt":"2026-04-18T23:04:31.032Z","lastSeenAt":"2026-04-24T13:02:27.004Z"}],"details":{"listingId":"c4237937-6d93-4efb-a6d6-60466330d8d3","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"kevintsengtw","slug":"dotnet-testing-fluentvalidation-testing","github":{"repo":"kevintsengtw/dotnet-testing-agent-skills","stars":23,"topics":["agent-skills","ai-assisted-development","copilot-skills","csharp","dotnet","dotnet-testing","github-copilot","integration-testing","testing","unit-testing","xunit"],"license":"mit","html_url":"https://github.com/kevintsengtw/dotnet-testing-agent-skills","pushed_at":"2026-03-31T07:28:56Z","description":"AI Agent Skills for .NET Testing - Based on 30-Day Testing Challenge (iThome Ironman 2025 Winner)","skill_md_sha":"8fde5d5d462e9daad095da274ce42be638c1114b","skill_md_path":"skills/dotnet-testing-fluentvalidation-testing/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/kevintsengtw/dotnet-testing-agent-skills/tree/main/skills/dotnet-testing-fluentvalidation-testing"},"layout":"multi","source":"github","category":"dotnet-testing-agent-skills","frontmatter":{"name":"dotnet-testing-fluentvalidation-testing","description":"測試 FluentValidation 驗證器的專門技能。當需要為 Validator 類別建立測試、驗證業務規則、測試錯誤訊息時使用。涵蓋 FluentValidation.TestHelper 完整使用、ShouldHaveValidationErrorFor、非同步驗證、跨欄位邏輯等。\nMake sure to use this skill whenever the user mentions FluentValidation testing, Validator testing, ShouldHaveValidationErrorFor, TestHelper, or testing business validation rules, even if they don't explicitly ask for validation testing guidance.\nKeywords: validator, 驗證器, fluentvalidation, validation testing, UserValidator, CreateOrderValidator, TestHelper, ShouldHaveValidationErrorFor, ShouldNotHaveValidationErrorFor, TestValidate, TestValidateAsync, 測試驗證器, 驗證業務規則"},"skills_sh_url":"https://skills.sh/kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-fluentvalidation-testing"},"updatedAt":"2026-04-24T13:02:27.004Z"}}