FOSMVVM ViewModel Test Generator
Generate test files for ViewModels following FOSMVVM testing patterns.
Conceptual Foundation
For full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference
ViewModel testing in FOSMVVM verifies three critical aspects:
- 1. Codable round-trip - ViewModel encodes and decodes without data loss
- Versioning stability - Structure hasn't changed unexpectedly
- Multi-locale translations - All
@LocalizedString properties have values in all supported locales
The LocalizableTestCase protocol provides infrastructure that tests all three in a single call.
When to Use This Skill
- - Creating tests for new ViewModels
- Adding test coverage to existing ViewModels
- Verifying localization completeness across locales
- Testing ViewModels with embedded/nested child ViewModels
- Verifying
@LocalizedSubs substitution behavior
What This Skill Generates
| File | Location | Purpose |
|---|
| INLINECODE3 | INLINECODE4 | Test suite conforming to INLINECODE5 |
| INLINECODE6 |
Tests/{Target}Tests/TestYAML/ | YAML translations for test (if needed) |
The Testing Pattern
Standard Pattern (Most Tests)
For most ViewModels, a single line provides complete coverage:
CODEBLOCK0
This verifies:
- - Codable encoding/decoding
- Versioned ViewModel stability
- Translations exist for all locales (en, es by default)
This is sufficient for the vast majority of ViewModel tests.
Extended Pattern (Specific Formatting Verification)
When testing specific formatting behavior (substitutions, compound strings), add locale-specific assertions:
CODEBLOCK1
This is optional - use only when verifying specific formatting techniques.
LocalizableTestCase Protocol
Test suites conform to LocalizableTestCase to access testing infrastructure:
CODEBLOCK2
The {ViewModelsTarget}.resourceAccess is the resource accessor defined when creating the ViewModels SPM target (via FOSResourceAccessor build tool plugin).
What LocalizableTestCase Provides
| Property/Method | Purpose |
|---|
| INLINECODE11 | Required - the localization store |
| INLINECODE12 |
Optional - locales to test (default: en, es) |
|
encoder(locale:) | Creates a localizing JSONEncoder |
|
en,
es,
enGB,
enUS | Locale constants |
Testing Methods
| Method | Use When |
|---|
| INLINECODE18 | Primary - complete ViewModel testing |
| INLINECODE19 |
Translation-only verification |
|
expectFullFieldValidationModelTests(_:) | Testing FieldValidationModel types |
|
expectFullFormFieldTests(_:) | Testing FormField instances |
|
expectCodable(_:encoder:) | Codable round-trip only |
|
expectVersionedViewModel(_:encoder:) | Versioning stability only |
YAML Requirements
ViewModels with @LocalizedString
Every ViewModel with @LocalizedString properties needs YAML entries:
CODEBLOCK3
CODEBLOCK4
Embedded ViewModels
When a ViewModel contains child ViewModels, all types in the hierarchy need YAML entries:
CODEBLOCK5
Both BoardViewModel and CardViewModel need YAML entries (can be in same or separate files).
Private Test ViewModels
When tests define private ViewModel structs for testing specific scenarios, those also need YAML:
CODEBLOCK6
Add entries to a test YAML file for these private types.
How to Use This Skill
Invocation:
/fosmvvm-viewmodel-test-generator
Prerequisites:
- - ViewModel structure understood from conversation context
- Localization properties identified (@LocalizedString, @LocalizedSubs, etc.)
- YAML localization files exist or will be created
- Child ViewModels identified (if any)
Workflow integration:
This skill is used when adding test coverage for ViewModels. The skill references conversation context automatically—no file paths or Q&A needed. Typically follows fosmvvm-viewmodel-generator.
Pattern Implementation
This skill references conversation context to determine test structure:
ViewModel Analysis
From conversation context, the skill identifies:
- - ViewModels to test (from prior discussion or codebase)
- Localization requirements (@LocalizedString properties)
- Child ViewModels (embedded within parent)
- Substitution behavior (@LocalizedSubs needing specific verification)
YAML Coverage Check
Verifies completeness:
- - ViewModel YAML entries (all @LocalizedString properties)
- Child ViewModel entries (nested types)
- Locale coverage (en, es, or project-specific locales)
Test File Generation
Creates test suite with:
- - LocalizableTestCase conformance
- Localization store initialization
- expectFullViewModelTests() calls for each ViewModel
- Optional specific formatting tests (substitutions, compound strings)
Context Sources
Skill references information from:
- - Prior conversation: ViewModels discussed or recently created
- ViewModel code: If Claude has read ViewModel files into context
- YAML files: From codebase analysis of existing localizations
- Test patterns: From existing test files in project
File Templates
See reference.md for complete file templates.
Common Scenarios
Testing a Single Top-Level ViewModel
CODEBLOCK7
Testing Multiple Related ViewModels
CODEBLOCK8
Testing with Custom Locales
CODEBLOCK9
Testing Substitution Behavior
CODEBLOCK10
Testing Embedded ViewModels
CODEBLOCK11
Troubleshooting
"Missing Translation" Error
CODEBLOCK12
Cause: YAML entry missing for a @LocalizedString property.
Fix: Add the property to the YAML file:
CODEBLOCK13
"Is pending localization" Error
Cause: The ViewModel wasn't encoded with a localizing encoder.
Fix: Ensure using encoder(locale:) or expectFullViewModelTests().
Test Passes But Translations Seem Wrong
Cause: YAML values exist but may have typos or wrong content.
Fix: Add specific assertions to verify exact values:
let vm = try .stub().toJSON(encoder: encoder(locale: en)).fromJSON()
#expect(try vm.title.localizedString == "Expected Value")
Naming Conventions
| Concept | Convention | Example |
|---|
| Test suite | INLINECODE30 | INLINECODE31 |
| Test file |
{Feature}ViewModelTests.swift |
DashboardViewModelTests.swift |
| YAML file |
{ViewModelName}.yml |
DashboardViewModel.yml |
| Test method |
{viewModelName}() or descriptive |
dashboardViewModel() |
See Also
Version History
| Version | Date | Changes |
|---|
| 1.0 | 2025-01-02 | Initial skill |
| 1.1 |
2026-01-19 | Updated LocalizableTestCase example to use {ViewModelsTarget}.resourceAccess pattern. |
| 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. |
FOSMVVM ViewModel 测试生成器
按照 FOSMVVM 测试模式为 ViewModel 生成测试文件。
概念基础
完整架构上下文,请参阅 FOSMVVMArchitecture.md | OpenClaw 参考
FOSMVVM 中的 ViewModel 测试验证三个关键方面:
- 1. Codable 往返 - ViewModel 编码和解码无数据丢失
- 版本稳定性 - 结构未发生意外变化
- 多语言翻译 - 所有 @LocalizedString 属性在所有支持的语言环境中都有值
LocalizableTestCase 协议提供了一次调用测试所有三者的基础设施。
何时使用此技能
- - 为新的 ViewModel 创建测试
- 为现有的 ViewModel 添加测试覆盖
- 验证跨语言环境的本地化完整性
- 测试包含嵌入/嵌套子 ViewModel 的 ViewModel
- 验证 @LocalizedSubs 替换行为
此技能生成的内容
| 文件 | 位置 | 用途 |
|---|
| {Name}ViewModelTests.swift | Tests/{Target}Tests/Localization/ | 符合 LocalizableTestCase 的测试套件 |
| {Name}ViewModel.yml |
Tests/{Target}Tests/TestYAML/ | 测试用的 YAML 翻译(如果需要) |
测试模式
标准模式(大多数测试)
对于大多数 ViewModel,一行代码即可提供完整覆盖:
swift
@Test func dashboardViewModel() throws {
try expectFullViewModelTests(DashboardViewModel.self)
}
此操作验证:
- - Codable 编码/解码
- 版本化的 ViewModel 稳定性
- 所有语言环境(默认 en, es)的翻译存在
这对于绝大多数 ViewModel 测试来说已经足够。
扩展模式(特定格式化验证)
当测试特定的格式化行为(替换、复合字符串)时,添加特定于语言环境的断言:
swift
@Test func greetingWithSubstitution() throws {
try expectFullViewModelTests(GreetingViewModel.self)
// 验证特定的替换行为
let vm: GreetingViewModel = try .stub()
.toJSON(encoder: encoder(locale: en))
.fromJSON()
#expect(try vm.welcomeMessage.localizedString == Welcome, John!)
}
这是可选的 - 仅在验证特定格式化技术时使用。
LocalizableTestCase 协议
测试套件符合 LocalizableTestCase 以访问测试基础设施:
swift
import FOSFoundation
@testable import FOSMVVM
import FOSTesting
import Foundation
import Testing
@testable import {ViewModelsTarget}
@Suite(我的 ViewModel 测试)
struct MyViewModelTests: LocalizableTestCase {
let locStore: LocalizationStore
init() throws {
self.locStore = try Self.loadLocalizationStore(
bundle: {ViewModelsTarget}.resourceAccess,
resourceDirectoryName:
)
}
}
{ViewModelsTarget}.resourceAccess 是在创建 ViewModels SPM 目标时(通过 FOSResourceAccessor 构建工具插件)定义的资源访问器。
LocalizableTestCase 提供的内容
| 属性/方法 | 用途 |
|---|
| locStore | 必需 - 本地化存储 |
| locales |
可选 - 要测试的语言环境(默认:en, es) |
| encoder(locale:) | 创建一个本地化 JSONEncoder |
| en, es, enGB, enUS | 语言环境常量 |
测试方法
| 方法 | 使用时机 |
|---|
| expectFullViewModelTests(:) | 主要 - 完整的 ViewModel 测试 |
| expectTranslations(:) |
仅翻译验证 |
| expectFullFieldValidationModelTests(_:) | 测试 FieldValidationModel 类型 |
| expectFullFormFieldTests(_:) | 测试 FormField 实例 |
| expectCodable(_:encoder:) | 仅 Codable 往返 |
| expectVersionedViewModel(_:encoder:) | 仅版本稳定性 |
YAML 要求
包含 @LocalizedString 的 ViewModel
每个具有 @LocalizedString 属性的 ViewModel 都需要 YAML 条目:
swift
@ViewModel
public struct DashboardViewModel: RequestableViewModel {
@LocalizedString public var pageTitle // 需要 YAML 条目
@LocalizedString public var emptyMessage // 需要 YAML 条目
public let itemCount: Int // 不需要 YAML
}
yaml
DashboardViewModel.yml
en:
DashboardViewModel:
pageTitle: Dashboard
emptyMessage: No items yet
es:
DashboardViewModel:
pageTitle: Tablero
emptyMessage: No hay elementos todavía
嵌入的 ViewModel
当 ViewModel 包含子 ViewModel 时,层次结构中的所有类型都需要 YAML 条目:
swift
@ViewModel
public struct BoardViewModel: RequestableViewModel {
@LocalizedString public var title
public let cards: [CardViewModel] // 子 ViewModel
}
@ViewModel
public struct CardViewModel {
@LocalizedString public var cardTitle
}
BoardViewModel 和 CardViewModel 都需要 YAML 条目(可以在同一个或不同的文件中)。
私有测试 ViewModel
当测试为测试特定场景定义私有 ViewModel 结构体时,这些也需要 YAML:
swift
// 在测试文件中
private struct TestParentViewModel: ViewModel {
@LocalizedString var title
let children: [TestChildViewModel]
}
private struct TestChildViewModel: ViewModel {
@LocalizedString var label
}
为这些私有类型在测试 YAML 文件中添加条目。
如何使用此技能
调用:
/fosmvvm-viewmodel-test-generator
前提条件:
- - 从对话上下文中理解 ViewModel 结构
- 已识别本地化属性(@LocalizedString, @LocalizedSubs 等)
- YAML 本地化文件已存在或将被创建
- 已识别子 ViewModel(如果有)
工作流集成:
此技能在添加 ViewModel 测试覆盖时使用。该技能自动引用对话上下文——无需文件路径或问答。通常跟在 fosmvvm-viewmodel-generator 之后。
模式实现
此技能引用对话上下文来确定测试结构:
ViewModel 分析
从对话上下文中,该技能识别:
- - 要测试的 ViewModel(来自之前的讨论或代码库)
- 本地化要求(@LocalizedString 属性)
- 子 ViewModel(嵌入在父级中)
- 替换行为(需要特定验证的 @LocalizedSubs)
YAML 覆盖检查
验证完整性:
- - ViewModel YAML 条目(所有 @LocalizedString 属性)
- 子 ViewModel 条目(嵌套类型)
- 语言环境覆盖(en, es 或项目特定的语言环境)
测试文件生成
创建包含以下内容的测试套件:
- - LocalizableTestCase 符合性
- 本地化存储初始化
- 每个 ViewModel 的 expectFullViewModelTests() 调用
- 可选的特定格式化测试(替换、复合字符串)
上下文来源
技能引用以下信息:
- - 之前的对话:讨论过或最近创建的 ViewModel
- ViewModel 代码:如果 Claude 已将 ViewModel 文件读入上下文
- YAML 文件:来自对现有本地化的代码库分析
- 测试模式:来自项目中现有的测试文件
文件模板
请参阅 reference.md 获取完整的文件模板。
常见场景
测试单个顶层 ViewModel
swift
@Test func dashboardViewModel() throws {
try expectFullViewModelTests(DashboardViewModel.self)
}
测试多个相关的 ViewModel
swift
@Test func boardViewModels() throws {
try expectFullViewModelTests(BoardViewModel.self)
try expectFullViewModelTests(ColumnViewModel.self)
try expectFullViewModelTests(CardViewModel.self)
}
使用自定义语言环境进行测试
swift
var locales: Set { [en, es, enGB] } // 覆盖默认值
@Test func multiLocaleViewModel() throws {
try expectFullViewModelTests(MyViewModel.self)
// 测试 en, es 和 en-GB
}
测试替换行为
swift
@Test func greetingSubstitutions() throws {
try expectFullViewModelTests(GreetingViewModel.self)
let vm: GreetingViewModel = try .stub(userName: Alice)
.toJSON(encoder: encoder(locale: en))
.fromJSON()
#expect(try vm.welcomeMessage.localizedString == Welcome, Alice!)
}
测试嵌入的 View