FOSMVVM ServerRequest 测试生成器:自动化 API 测试 - Openclaw Skills
作者:互联网
2026-03-30
什么是 FOSMVVM ServerRequest 测试生成器?
FOSMVVM ServerRequest 测试生成器是 Openclaw Skills 生态系统中的专用工具,旨在简化服务端 Swift 单元测试和集成测试的创建。通过利用 VaporTesting 框架,该技能消除了手动构建 URL 和易错的 JSON 解码需求,为开发者提供了一个类型安全的环境来验证 Show、Create、Update 和 Delete 操作。这种方法确保了服务器响应、错误处理和多语言支持通过全栈流程得到严格测试,从而在基于 FOSMVVM 的项目中保持高代码质量和架构一致性。
该技能具有高度的上下文感知能力,能够参考之前的对话和现有代码来确定适当的测试结构,而无需手动输入文件路径。对于希望自动化 API 契约验证并确保向客户端正确交付本地化内容的开发者来说,这是一个必不可少的组件。
下载入口:https://github.com/openclaw/skills/tree/main/skills/foscomputerservices/fosmvvm-serverrequest-test-generator
安装与下载
1. ClawHub CLI
从源直接安装技能的最快方式。
npx clawhub@latest install fosmvvm-serverrequest-test-generator
2. 手动安装
将技能文件夹复制到以下位置之一
全局模式~/.openclaw/skills/
工作区
/skills/
优先级:工作区 > 本地 > 内置
3. 提示词安装
将此提示词复制到 OpenClaw 即可自动安装。
请帮我使用 Clawhub 安装 fosmvvm-serverrequest-test-generator。如果尚未安装 Clawhub,请先安装(npm i -g clawhub)。
FOSMVVM ServerRequest 测试生成器 应用场景
- 验证 CRUD 操作(如 Show、Create、Update 和 Delete)的服务器响应。
- 测试 API 端点中复杂的错误处理场景和边缘情况。
- 验证跨不同语言的多语言响应体和本地化字符串。
- 自动化客户端请求类型与服务端控制器之间的集成测试。
- 为新功能生成全面的测试套件,无需编写繁琐的模板代码。
- 该技能分析对话上下文,识别特定的 ServerRequest 类型、请求协议和相关的 ResponseBody 类型。
- 它检测项目环境,包括目标名称、现有测试模式和本地化设置。
- 制定全面的测试场景计划,涵盖成功路径、验证失败和多语言验证。
- 生成器生成一个符合 VaporTesting 模式的完整 Swift 测试文件,包括必要的导入和 @Suite 定义。
- 生成的代码包含应用程序设置辅助工具 (withTestApp) 和路由注册逻辑,以确保测试环境与生产服务器配置一致。
FOSMVVM ServerRequest 测试生成器 配置指南
要在 Openclaw Skills 框架中使用此工具,请确保您的服务端 Swift 项目已配置必要的 FOSMVVM 和 VaporTesting 依赖项。
- 使用以下命令调用技能:
/fosmvvm-serverrequest-test-generator
- 确保 AI 代理的上下文中存在 ServerRequest 和 Controller 实现。
- 使用 Swift 软件包管理器运行生成的测试:
swift test
FOSMVVM ServerRequest 测试生成器 数据架构与分类体系
| 文件 | 位置 | 用途 |
|---|---|---|
| {Feature}RequestTests.swift | Tests/{Target}Tests/Requests/ | 包含服务器请求类型化验证逻辑的主要测试套件。 |
| 测试 YAML | Tests/{Target}Tests/TestYAML/ | 用于验证 ViewModel 中翻译字符串的本地化资源文件。 |
| TestingServerRequestResponse | Infrastructure | 提供对 HTTP 状态、响应头、正文和错误的类型化访问包装器。 |
name: fosmvvm-serverrequest-test-generator
description: Generate ServerRequest tests using VaporTesting. Covers typed request/response validation for Show, Create, Update, and Delete operations.
homepage: https://github.com/foscomputerservices/FOSUtilities
metadata: {"clawdbot": {"emoji": "??", "os": ["darwin", "linux"]}}
FOSMVVM ServerRequest Test Generator
Generate test files for ServerRequest types using VaporTesting infrastructure.
Conceptual Foundation
For full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference
ServerRequest testing uses VaporTesting infrastructure to send typed requests through the full server stack:
┌─────────────────────────────────────────────────────────────────────┐
│ ServerRequest Test Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Test Code: │
│ let request = MyRequest(query: .init(...)) │
│ app.testing().test(request, locale: en) { response in } │
│ │
│ Infrastructure handles: │
│ ? Path derivation from type name (MyRequest → /my) │
│ ? HTTP method from action (ShowRequest → GET) │
│ ? Query/body encoding │
│ ? Header injection (locale, version) │
│ ? Response decoding to ResponseBody type │
│ │
│ You verify: │
│ ? response.status (HTTPStatus) │
│ ? response.body (R.ResponseBody? - typed!) │
│ ? response.error (R.ResponseError? - typed!) │
│ │
└─────────────────────────────────────────────────────────────────────┘
STOP AND READ THIS
Testing ServerRequests uses VaporTesting infrastructure. No manual URL construction. Ever.
┌──────────────────────────────────────────────────────────────────────┐
│ SERVERREQUEST TESTING USES TestingApplicationTester │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Configure Vapor Application with routes │
│ 2. Use app.testing().test(request, locale:) { response in } │
│ 3. Verify response.status, response.body, response.error │
│ │
│ TestingServerRequestResponse provides TYPED access to: │
│ ? status: HTTPStatus │
│ ? headers: HTTPHeaders │
│ ? body: R.ResponseBody? ← Auto-decoded! │
│ ? error: R.ResponseError? ← Auto-decoded! │
│ │
└──────────────────────────────────────────────────────────────────────┘
What You Must NEVER Do
// ? WRONG - manual URL construction
let url = URL(string: "http://localhost:8080/my_request?query=value")!
let response = try await URLSession.shared.data(from: url)
// ? WRONG - string path with method
try await app.test(.GET, "/my_request") { response in }
// ? WRONG - manual JSON encoding/decoding
let json = try JSONEncoder().encode(requestBody)
let decoded = try JSONDecoder().decode(ResponseBody.self, from: data)
// ? WRONG - constructing TestingHTTPRequest manually
let httpRequest = TestingHTTPRequest(method: .GET, url: "/path", headers: headers)
try await app.testing().performTest(request: httpRequest)
What You Must ALWAYS Do
// ? RIGHT - Use TestingApplicationTester.test() with ServerRequest
let request = MyShowRequest(query: .init(userId: userId))
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .ok)
#expect(response.body?.viewModel.name == "Expected Name")
}
// ? RIGHT - Test multiple locales
for locale in [en, es] {
try await app.testing().test(request, locale: locale) { response in
#expect(response.status == .ok)
// Localized values are automatically handled
}
}
// ? RIGHT - Test error responses
let badRequest = MyShowRequest(query: .init(userId: invalidId))
try await app.testing().test(badRequest, locale: en) { response in
#expect(response.status == .notFound)
#expect(response.error != nil)
}
The path is derived from the ServerRequest type. HTTP method comes from the action. Headers are automatic. You NEVER write URL strings or decode JSON manually.
When to Use This Skill
- Testing any ServerRequest implementation
- Verifying server responses for CRUD operations
- Testing error handling and edge cases
- Multi-locale response verification
- Integration testing between client request types and server controllers
If you're about to write URLSession, app.test(.GET, "/path"), or manual JSON decoding, STOP and use this skill instead.
What This Skill Generates
| File | Location | Purpose |
|---|---|---|
{Feature}RequestTests.swift |
Tests/{Target}Tests/Requests/ |
Test suite for ServerRequest |
| Test YAML (if needed) | Tests/{Target}Tests/TestYAML/ |
Localization for test ViewModels |
Project Structure Configuration
| Placeholder | Description | Example |
|---|---|---|
{Feature} |
Feature or entity name (PascalCase) | Idea, User, Dashboard |
{Target} |
Server test target | WebServerTests, AppTests |
{ViewModelsTarget} |
Shared ViewModels SPM target | ViewModels |
{WebServerTarget} |
Server-side target | WebServer, AppServer |
{ResourceDir} |
YAML resource directory | TestYAML, Resources |
Key Types
TestingServerRequestResponse
Wraps HTTP response with typed access:
| Property | Type | Description |
|---|---|---|
status |
HTTPStatus |
HTTP status code (.ok, .notFound, etc.) |
headers |
HTTPHeaders |
Response headers |
body |
R.ResponseBody? |
Typed response body (auto-decoded) |
error |
R.ResponseError? |
Typed error (auto-decoded) |
TestingApplicationTester Extension
func test(
_ request: R,
locale: Locale = en,
headers: HTTPHeaders = [:],
afterResponse: (TestingServerRequestResponse) async throws -> Void
) async throws -> any TestingApplicationTester
Convenience Locales
Available on TestingApplicationTester:
en- EnglishenUS- English (US)enGB- English (UK)es- Spanish
Test Structure
Basic Test Suite
import FOSFoundation
@testable import FOSMVVM
import FOSTesting
import FOSTestingVapor
import Foundation
import Testing
import Vapor
import VaporTesting
@Suite("MyFeature Request Tests")
struct MyFeatureRequestTests {
@Test func showRequest_success() async throws {
try await withTestApp { app in
let request = MyShowRequest(query: .init(id: validId))
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .ok)
#expect(response.body?.viewModel != nil)
}
}
}
@Test func showRequest_notFound() async throws {
try await withTestApp { app in
let request = MyShowRequest(query: .init(id: invalidId))
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .notFound)
}
}
}
}
private func withTestApp(_ test: (Application) async throws -> Void) async throws {
try await withApp { app in
// Configure routes
try app.routes.register(collection: MyController())
try await test(app)
}
}
Testing Different Request Types
| Request Type | HTTP Method | What to Test |
|---|---|---|
ShowRequest |
GET | Query params, response body, localization |
ViewModelRequest |
GET | ViewModel population, all localized fields |
CreateRequest |
POST | RequestBody validation, created entity, ID response |
UpdateRequest |
PATCH | RequestBody validation, updated entity, response |
DeleteRequest |
DELETE | Entity removal, status code |
How to Use This Skill
Invocation: /fosmvvm-serverrequest-test-generator
Prerequisites:
- ServerRequest type understood from conversation context
- Test scenarios identified (success paths, error paths, validation)
- Controller implementation exists or is being created
- VaporTesting infrastructure understood
Workflow integration: This skill is used when testing ServerRequest implementations. The skill references conversation context automatically—no file paths or Q&A needed. Typically follows fosmvvm-serverrequest-generator.
Pattern Implementation
This skill references conversation context to determine test structure:
Request Analysis
From conversation context, the skill identifies:
- ServerRequest type (from prior discussion or server implementation)
- Request protocol (ShowRequest, CreateRequest, UpdateRequest, etc.)
- ResponseBody type (ViewModel or simple structure)
- ResponseError type (custom errors or EmptyError)
Test Scenario Planning
Based on operation semantics:
- Success paths (valid input, expected output)
- Error paths (not found, validation failure, business logic errors)
- Localization (if ResponseBody has localized fields)
- Multi-locale (testing across supported locales)
Infrastructure Detection
From project state:
- Existing test patterns (similar test files in codebase)
- Localization setup (YAML fixtures needed)
- Database requirements (seed data for tests)
Test File Generation
- Test suite conforming to VaporTesting patterns
- One @Test function per scenario
- withTestApp helper for application setup
- Route registration
- Request invocations using app.testing().test()
Context Sources
Skill references information from:
- Prior conversation: Test requirements, scenarios discussed
- ServerRequest: If Claude has read ServerRequest code into context
- Controller: From server implementation
- Existing tests: From codebase analysis of similar test files
Common Scenarios
Testing ViewModelRequest with Localization
@Test func viewModelRequest_multiLocale() async throws {
try await withTestApp { app in
let request = DashboardViewModelRequest()
// Test English
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .ok)
let vm = try #require(response.body)
#expect(try vm.pageTitle.localizedString == "Dashboard")
}
// Test Spanish
try await app.testing().test(request, locale: es) { response in
#expect(response.status == .ok)
let vm = try #require(response.body)
#expect(try vm.pageTitle.localizedString == "Tablero")
}
}
}
Testing CreateRequest with Validation
@Test func createRequest_validInput() async throws {
try await withTestApp { app in
let request = CreateIdeaRequest(requestBody: .init(
content: "Valid idea content"
))
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .ok)
#expect(response.body?.id != nil)
}
}
}
@Test func createRequest_invalidInput() async throws {
try await withTestApp { app in
let request = CreateIdeaRequest(requestBody: .init(
content: "" // Empty content should fail validation
))
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .badRequest)
#expect(response.error != nil)
}
}
}
Testing UpdateRequest
@Test func updateRequest_success() async throws {
try await withTestApp { app in
// First create an entity
let createRequest = CreateIdeaRequest(requestBody: .init(content: "Original"))
var createdId: ModelIdType?
try await app.testing().test(createRequest, locale: en) { response in
createdId = response.body?.id
}
// Then update it
let updateRequest = UpdateIdeaRequest(requestBody: .init(
ideaId: try #require(createdId),
content: "Updated content"
))
try await app.testing().test(updateRequest, locale: en) { response in
#expect(response.status == .ok)
#expect(response.body?.viewModel.content == "Updated content")
}
}
}
Testing DeleteRequest
@Test func deleteRequest_success() async throws {
try await withTestApp { app in
// Create, then delete
let deleteRequest = DeleteIdeaRequest(requestBody: .init(ideaId: existingId))
try await app.testing().test(deleteRequest, locale: en) { response in
#expect(response.status == .ok)
}
// Verify deleted (should return not found)
let showRequest = ShowIdeaRequest(query: .init(ideaId: existingId))
try await app.testing().test(showRequest, locale: en) { response in
#expect(response.status == .notFound)
}
}
}
Testing ShowRequest with Query Parameters
@Test func showRequest_withQuery() async throws {
try await withTestApp { app in
let request = UserShowRequest(query: .init(
userId: userId,
includeDetails: true
))
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .ok)
#expect(response.body?.user.details != nil)
}
}
}
Testing ServerRequestError Localizations
Why Error Localization Testing is Different
Unlike ViewModels, ServerRequestError types:
- Are often enums, not structs
- Do not conform to
StubbableorRetrievablePropertyNames - Cannot use
expectTranslations(ErrorType.self)like ViewModels
This means you must manually test each error case individually.
The Pattern
Use LocalizableTestCase.expectTranslations(_ localizable:) on each error's Localizable property:
@Suite("MyError Localization Tests")
struct MyErrorLocalizationTests: LocalizableTestCase {
let locStore: LocalizationStore
init() throws {
self.locStore = try Self.loadLocalizationStore(
bundle: Bundle.module,
resourceDirectoryName: "TestYAML"
)
}
@Test func errorMessages_simpleErrors() throws {
// Test each error case individually
let serverFailed = MyError(code: .serverFailed)
try expectTranslations(serverFailed.message)
let appFailed = MyError(code: .applicationFailed)
try expectTranslations(appFailed.message)
}
@Test func errorMessages_withSubstitutions() throws {
// For errors with associated values, test with representative values
let quotaError = QuotaError(code: .quotaExceeded(requested: 100, maximum: 50))
try expectTranslations(quotaError.message)
}
}
Testing Error Messages in Integration Tests
When testing the full request/response cycle, verify error messages resolve:
@Test func createRequest_validationError_hasLocalizedMessage() async throws {
try await withTestApp { app in
let request = CreateIdeaRequest(requestBody: .init(content: ""))
try await app.testing().test(request, locale: en) { response in
#expect(response.status == .badRequest)
let error = try #require(response.error)
// Verify the message resolved (not empty or pending)
#expect(!error.message.isEmpty)
// Optionally verify specific text for English locale
#expect(try error.message.localizedString.contains("required"))
}
}
}
Why Not Stubbable?
Stubbable works well for ViewModels because:
- ViewModels are structs with many properties
- A single
stub()provides a complete test instance
ServerRequestError types are often enums where:
- Each case may have different associated values
- Each case may have a different localized message
- A single
stub()can't cover all cases
You must enumerate and test each error case explicitly.
Checklist for Error Localization Tests
- Test each enum case for simple errors
- Test representative associated values for parameterized errors
- Verify messages resolve (not empty) for all configured locales
- Verify substitution placeholders are replaced in
LocalizableSubstitutions
Troubleshooting
"Route not found" Error
Cause: Controller not registered in test app.
Fix: Register the controller before testing:
try app.routes.register(collection: MyController())
Response body is nil but status is .ok
Cause: JSON decoding failed silently.
Fix: Check that ResponseBody type matches server response exactly. Use response.headers to verify Content-Type.
Localization not applied
Cause: Locale not passed to encoder.
Fix: The test(_:locale:) method handles this automatically. Ensure you're passing the locale parameter.
"Missing Translation" in Response
Cause: YAML localization not loaded.
Fix: Initialize localization store in test app setup:
try app.initYamlLocalization(
bundle: Bundle.module,
resourceDirectoryName: "TestYAML"
)
Naming Conventions
| Concept | Convention | Example |
|---|---|---|
| Test suite | {Feature}RequestTests |
IdeaRequestTests |
| Test file | {Feature}RequestTests.swift |
IdeaRequestTests.swift |
| Test method (success) | {action}Request_success |
showRequest_success |
| Test method (error) | {action}Request_{errorCase} |
showRequest_notFound |
| Test method (validation) | {action}Request_{validationCase} |
createRequest_emptyContent |
| Test helper | withTestApp |
withTestApp { app in } |
| Locale constant | en, es, enUS, enGB |
locale: en |
File Templates
See reference.md for complete file templates.
See Also
- FOSMVVMArchitecture.md - Full architecture
- fosmvvm-serverrequest-generator - Creating ServerRequest types
- fosmvvm-viewmodel-test-generator - Testing ViewModels (localization only)
- reference.md - Complete file templates
Version History
| Version | Date | Changes |
|---|---|---|
| 1.1 | 2025-01-20 | Add ServerRequestError localization testing guidance |
| 1.2 | 2026-01-24 | Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths. |
| 1.0 | 2025-01-05 | Initial skill |
相关推荐
专题
+ 收藏
+ 收藏
+ 收藏
+ 收藏
+ 收藏
最新数据
相关文章
信号管道:自动化营销情报工具 - Openclaw Skills
技能收益追踪器:监控 Openclaw 技能并实现变现
AI 合规准备就绪度:评估与治理工具 - Openclaw Skills
FOSMVVM ServerRequest 测试生成器:自动化 API 测试 - Openclaw Skills
酒店搜索器:AI 赋能的住宿与位置情报 - Openclaw Skills
Dub 链接 API:程序化链接管理 - Openclaw Skills
IntercomSwap:P2P BTC 与 USDT 跨链兑换 - Openclaw Skills
spotplay:macOS 原生 Spotify 播放控制 - Openclaw Skills
DeepSeek OCR:AI驱动的图像文本识别 - Openclaw Skills
Web Navigator:自动化网页研究与浏览 - Openclaw Skills
AI精选
