Claude-skill-registry-data manage-fake
Create or update Fake test doubles for TypeScript interfaces. Use when user asks to "create a fake", "update a fake", "generate test fake", "fix fake", "manage fake", or mentions needing a fake for testing. Generates Initializer interface, Fake class, and Builder class following the testing pattern with getMockingFunction, FakeEntity inheritance, and EventBroker support.
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/manage-fake" ~/.claude/skills/majiayu000-claude-skill-registry-data-manage-fake && rm -rf "$T"
data/manage-fake/SKILL.mdManage Fake Skill
This skill helps you create or update a Fake class for testing purposes. Fakes are test doubles that implement interfaces with configurable behavior.
When to Use This Skill
Use this skill when you need to:
- Create a new Fake implementation for testing an interface
- Update an existing Fake to match interface changes
The skill will generate/update:
- A Fake class that implements the interface
- An Initializer interface for required parameters
- A Builder class for easy test instantiation
Usage
Invoke this skill when the user asks to:
- "Create a fake for [InterfaceName]"
- "Generate a test fake for [InterfaceName]"
- "I need a fake [InterfaceName]"
- "Update the fake for [InterfaceName]"
- "The [InterfaceName] interface changed, update its fake"
- "Fix the Fake[ClassName] to match the interface"
Prerequisites
Before creating/updating a fake:
- Verify the interface exists - The interface you're faking must already be defined
- Check for existing fake - Use Glob to search for existing
filesFake[ClassName] - Identify if interface extends IEntity - This affects the pattern used
Create vs Update Decision
If fake file exists: Update mode
- Read the existing fake file
- Read the interface definition
- Compare and identify differences
- Update the fake to match the interface
If fake file does NOT exist: Create mode
- Read the interface definition
- Generate all three components from scratch
Naming Convention
For an interface named
IMyClass, create:
- File:
FakeMyClass.ts - Initializer:
FakeMyClassInitializer - Fake class:
FakeMyClass - Builder class:
FakeMyClassBuilder
Pattern: Remove the
I prefix from the interface name and add Fake prefix.
Required Imports
import { getMockingFunction } from '@zdr-tools/zdr-testing-tools'; import { FakeEntity, FakeEntityBuilder, FakesFactory, type IFakeEntityInitialData } from '@zdr-tools/zdr-entities/fakes'; import type { IEntity, IPropEventBroker, IReadablePropEventBroker, IEntityCollection, IOrderedEntityCollection, IRestorablePropEventBroker } from '@zdr-tools/zdr-entities';
Structure
1. Initializer Interface
Purpose: Defines all required parameters for creating the fake.
Rules:
- All fields are MANDATORY (no optional fields with
)? - One field per interface field
- One field per interface method (for return values only, named
)[methodName]ReturnValue
Standard Pattern:
export interface FakeMyClassInitializer { someField: string; anotherField: number; someMethodReturnValue: boolean; }
If interface extends IEntity:
export interface FakeMyClassInitializer extends IFakeEntityInitialData { someField: string; anotherField: number; someMethodReturnValue: boolean; }
2. Fake Class
Purpose: The actual fake implementation of the interface.
Standard Pattern:
export class FakeMyClass implements IMyClass { public someField: string; public anotherField: number; constructor(private fakeMyClassInitialData: FakeMyClassInitializer) { this.someField = fakeMyClassInitialData.someField; this.anotherField = fakeMyClassInitialData.anotherField; } someMethod = getMockingFunction<(arg: string) => boolean>(() => { return this.fakeMyClassInitialData.someMethodReturnValue; }); }
If interface extends IEntity:
export class FakeMyClass extends FakeEntity implements IMyClass { public someField: string; public anotherField: number; constructor(private fakeMyClassInitialData: FakeMyClassInitializer) { super(fakeMyClassInitialData); this.someField = fakeMyClassInitialData.someField; this.anotherField = fakeMyClassInitialData.anotherField; } someMethod = getMockingFunction<(arg: string) => boolean>(() => { return this.fakeMyClassInitialData.someMethodReturnValue; }); }
Method Implementation Rules:
- Use
to wrap methodsgetMockingFunction - Generic type matches the method signature
- Return value comes from
field in initializer[methodName]ReturnValue - Method arguments are NOT stored in initializer, only return values
3. Builder Class
Purpose: Provides a fluent API for creating fakes with sensible defaults.
Standard Pattern:
export class FakeMyClassBuilder { private someField: string = ''; private anotherField: number = 0; private someMethodReturnValue: boolean = false; withSomeField(value: string): this { this.someField = value; return this; } withAnotherField(value: number): this { this.anotherField = value; return this; } withSomeMethodReturnValue(value: boolean): this { this.someMethodReturnValue = value; return this; } protected getFakeMyClassInitializer(): FakeMyClassInitializer { return { someField: this.someField, anotherField: this.anotherField, someMethodReturnValue: this.someMethodReturnValue, }; } build(): FakeMyClass { return new FakeMyClass(this.getFakeMyClassInitializer()); } }
If interface extends IEntity:
export class FakeMyClassBuilder extends FakeEntityBuilder { private someField: string = ''; private anotherField: number = 0; private someMethodReturnValue: boolean = false; withSomeField(value: string): this { this.someField = value; return this; } withAnotherField(value: number): this { this.anotherField = value; return this; } withSomeMethodReturnValue(value: boolean): this { this.someMethodReturnValue = value; return this; } protected getFakeMyClassInitializer(): FakeMyClassInitializer { return { ...this.getInitialData(), // From FakeEntityBuilder someField: this.someField, anotherField: this.anotherField, someMethodReturnValue: this.someMethodReturnValue, }; } build(): FakeMyClass { return new FakeMyClass(this.getFakeMyClassInitializer()); } }
Default Values for Builder Fields
Use these default values for different field types:
| Type | Default Value |
|---|---|
| Fields that can be undefined | |
| |
| |
| |
| |
| |
| |
| |
| |
| Entity/Model types | |
EventBroker Special Handling
For EventBroker fields, create TWO "with" methods:
// Field definition private name: IPropEventBroker<string> = FakesFactory.createPropEventBroker<string>(''); // Method 1: Accept the full EventBroker withName(value: IPropEventBroker<string>): this { this.name = value; return this; } // Method 2: Accept just the value (convenience method) withNameValue(value: string): this { return this.withName(FakesFactory.createPropEventBroker<string>(value)); }
File Location and Export Structure
IMPORTANT: All fakes MUST be organized in a
/fakes folder at the package root level (sibling to /src):
Directory Structure
packages/ my-package/ src/ IMyClass.ts # Interface definitions MyClass.ts fakes/ # Sibling to src, NOT inside src index.ts # Exports all fakes FakeMyClass.ts # Fake implementation FakeOtherClass.ts
Rules
-
Fakes folder location: Always create fakes in
at the package root (sibling to/fakes/
)/src- If interface is in
packages/my-package/src/IMyClass.ts - Create fake in
(NOT inpackages/my-package/fakes/FakeMyClass.ts
)src/fakes/
- If interface is in
-
Index file: The
folder MUST contain an/fakes
file that exports all fakesindex.ts- If
doesn't exist, create itindex.ts - Export pattern:
export * from './FakeMyClass';
- If
-
After creating/updating a fake: Always add/verify the export in
/fakes/index.ts
Example index.ts
// fakes/index.ts (at package root, sibling to src/) export * from './FakeReport'; export * from './FakeUser'; export * from './FakeWorkspace';
Workflow
Create Workflow (No existing fake)
- Identify the interface - Ask user which interface to fake if not clear
- Locate the interface file - Use Glob to find the interface definition
- Read the interface - Get all fields and methods
- Check if interface extends IEntity - This affects the pattern used
- Ensure
folder exists - Check if/fakes
directory exists at package root (sibling to/fakes/
), create if needed/src - Create the fake file in
with all three components:/fakes/FakeMyClass.ts- Initializer interface
- Fake class
- Builder class
- Update the exports index:
- Check if
exists, create if it doesn't/fakes/index.ts - Add export line:
export * from './FakeMyClass'; - Keep exports alphabetically sorted
- Check if
Update Workflow (Fake exists)
- Identify the interface and fake - Determine which interface/fake to update
- Read both files:
- Read the interface definition
- Read the existing fake file
- Compare and identify changes:
- Added fields - Add to initializer, fake class constructor, and builder (with default value and
method)with* - Removed fields - Remove from all three components
- Changed field types - Update type in all three components and adjust default value in builder
- Added methods - Add
to initializer, add method implementation in fake class with[methodName]ReturnValue
, add field andgetMockingFunction
method to builderwith* - Removed methods - Remove
and all related code[methodName]ReturnValue - Changed method signatures - Update the generic type in
and the return value typegetMockingFunction
- Added fields - Add to initializer, fake class constructor, and builder (with default value and
- Apply updates using Edit tool:
- Update the Initializer interface
- Update the Fake class fields, constructor, and methods
- Update the Builder class fields,
methods, and the protected getter methodwith*
- Verify completeness - Ensure all interface members are represented in the fake
Update Guidelines
When updating an existing fake:
- Preserve existing structure - Don't rewrite the entire file, use Edit tool for targeted changes
- Maintain consistency - Follow the same patterns used in the existing fake
- Keep default values sensible - When adding new builder fields, use appropriate defaults from the table below
- Handle EventBrokers correctly - If adding an EventBroker field, remember to create BOTH
methodswith* - Check inheritance - If the interface extends IEntity and the fake doesn't extend FakeEntity, this is a structural change that may require a larger refactor
Common Update Scenarios
Scenario 1: Adding a New Field
Interface change:
export interface IReport extends IEntity { title: string; status: string; description: string; // NEW FIELD }
Required updates:
- Add to
:FakeReportInitializerdescription: string; - Add to
class:FakeReportpublic description: string; - Add to constructor:
this.description = fakeReportInitialData.description; - Add to
:FakeReportBuilderprivate description: string = ''; - Add builder method:
withDescription(value: string): this { this.description = value; return this; }
- Add to builder's
return object:getFakeReportInitializer()description: this.description,
Scenario 2: Adding a New Method
Interface change:
export interface IReport extends IEntity { // ... existing fields validate(): Promise<boolean>; // NEW METHOD }
Required updates:
- Add to
:FakeReportInitializervalidateReturnValue: Promise<boolean>; - Add to
class:FakeReport
validate = getMockingFunction<() => Promise<boolean>>(() => { return this.fakeReportInitialData.validateReturnValue; });
- Add to
:FakeReportBuilderprivate validateReturnValue: Promise<boolean> = Promise.resolve(false); - Add builder method:
withValidateReturnValue(value: Promise<boolean>): this { this.validateReturnValue = value; return this; }
- Add to builder's
return object:getFakeReportInitializer()validateReturnValue: this.validateReturnValue,
Scenario 3: Removing a Field
Interface change:
export interface IReport extends IEntity { title: string; // status: string; // REMOVED }
Required updates:
- Remove from
:FakeReportInitializerstatus: string; - Remove from
class:FakeReportpublic status: string; - Remove from constructor:
this.status = fakeReportInitialData.status; - Remove from
:FakeReportBuilderprivate status: string = ''; - Remove builder method:
withStatus(...) - Remove from builder's
return object:getFakeReportInitializer()status: this.status,
Scenario 4: Changing a Field Type
Interface change:
export interface IReport extends IEntity { title: string; status: ReportStatus; // Changed from string to enum/type }
Required updates:
- Update in
:FakeReportInitializerstatus: ReportStatus; - Update in
class:FakeReportpublic status: ReportStatus; - Constructor assignment stays the same:
this.status = fakeReportInitialData.status; - Update in
with appropriate default:FakeReportBuilderprivate status: ReportStatus = ReportStatus.Draft; - Update builder method parameter:
withStatus(value: ReportStatus): this - Builder's getter stays the same:
status: this.status,
Scenario 5: Adding an EventBroker Field
Interface change:
export interface IReport extends IEntity { title: IPropEventBroker<string>; // Changed from string to EventBroker }
Required updates:
- Update in
:FakeReportInitializertitle: IPropEventBroker<string>; - Update in
class:FakeReportpublic title: IPropEventBroker<string>; - Constructor assignment stays the same:
this.title = fakeReportInitialData.title; - Update in
:FakeReportBuilderprivate title: IPropEventBroker<string> = FakesFactory.createPropEventBroker<string>(''); - Add/update TWO builder methods:
withTitle(value: IPropEventBroker<string>): this { this.title = value; return this; } withTitleValue(value: string): this { return this.withTitle(FakesFactory.createPropEventBroker<string>(value)); }
- Builder's getter stays the same:
title: this.title,
Example Reference
See
examples.md in the same directory as this skill for complete working examples.
Important Notes
File Organization (CRITICAL)
- All fakes MUST be in
directory at package root (sibling to/fakes/
, NOT inside/src
)/src - All fakes MUST be exported from
/fakes/index.ts - After creating or updating any fake, verify the export exists in
index.ts - Keep exports in
alphabetically sorted for maintainabilityindex.ts
General Patterns
- All initializer fields are MANDATORY (never use optional
)? - Methods only track return values, not arguments
- Use
for all method implementationsgetMockingFunction - Builder fields should have sensible defaults
- EventBroker fields need two "with" methods
- Always return
from builder "with" methods for chainingthis - Protected
returns the initializer objectgetFake[ClassName]Initializer() - Public
creates the fake instancebuild()
When Updating
- Always use the Edit tool for updates, not Write (which overwrites the entire file)
- Update all three components (Initializer, Fake class, Builder) for consistency
- When adding fields/methods, follow the exact same pattern as existing ones
- Check for TypeScript errors after updates to ensure completeness
- If the interface structure changed significantly (e.g., now extends IEntity), consider regenerating instead of updating
- Verify the export still exists in
after updates/fakes/index.ts