| name | manage-fake |
| description | 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. |
Manage 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
Fake[ClassName]files - 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
getMockingFunctionto wrap methods - Generic type matches the method signature
- Return value comes from
[methodName]ReturnValuefield in initializer - 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 | undefined |
string |
'' |
number |
0 |
boolean |
false |
IReadablePropEventBroker<T> |
FakesFactory.createReadablePropEventBroker<T>(defaultValueForT) |
IPropEventBroker<T> |
FakesFactory.createPropEventBroker<T>(defaultValueForT) |
IRestorablePropEventBroker<T> |
FakesFactory.createFakeRestorablePropEventBroker<T>(defaultValueForT) |
IEntityCollection<T> |
FakesFactory.createEntityCollection<T>([]) |
IOrderedEntityCollection<T> |
FakesFactory.createOrderedEntityCollection<T>([]) |
| Entity/Model types | new Fake[EntityName]Builder().build() |
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
/fakes/at the package root (sibling to/src)- If interface is in
packages/my-package/src/IMyClass.ts - Create fake in
packages/my-package/fakes/FakeMyClass.ts(NOT insrc/fakes/)
- If interface is in
Index file: The
/fakesfolder MUST contain anindex.tsfile that exports all fakes- If
index.tsdoesn't exist, create it - 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
/fakesfolder exists - Check if/fakes/directory exists at package root (sibling to/src), create if needed - Create the fake file in
/fakes/FakeMyClass.tswith all three components:- Initializer interface
- Fake class
- Builder class
- Update the exports index:
- Check if
/fakes/index.tsexists, create if it doesn't - 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
with*method) - 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
[methodName]ReturnValueto initializer, add method implementation in fake class withgetMockingFunction, add field andwith*method to builder - Removed methods - Remove
[methodName]ReturnValueand all related code - Changed method signatures - Update the generic type in
getMockingFunctionand the return value type
- 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,
with*methods, and the protected getter method
- 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
with*methods - 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
FakeReportInitializer:description: string; - Add to
FakeReportclass:public description: string; - Add to constructor:
this.description = fakeReportInitialData.description; - Add to
FakeReportBuilder:private description: string = ''; - Add builder method:
withDescription(value: string): this {
this.description = value;
return this;
}
- Add to builder's
getFakeReportInitializer()return object: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
FakeReportInitializer:validateReturnValue: Promise<boolean>; - Add to
FakeReportclass:
validate = getMockingFunction<() => Promise<boolean>>(() => {
return this.fakeReportInitialData.validateReturnValue;
});
- Add to
FakeReportBuilder:private validateReturnValue: Promise<boolean> = Promise.resolve(false); - Add builder method:
withValidateReturnValue(value: Promise<boolean>): this {
this.validateReturnValue = value;
return this;
}
- Add to builder's
getFakeReportInitializer()return object:validateReturnValue: this.validateReturnValue,
Scenario 3: Removing a Field
Interface change:
export interface IReport extends IEntity {
title: string;
// status: string; // REMOVED
}
Required updates:
- Remove from
FakeReportInitializer:status: string; - Remove from
FakeReportclass:public status: string; - Remove from constructor:
this.status = fakeReportInitialData.status; - Remove from
FakeReportBuilder:private status: string = ''; - Remove builder method:
withStatus(...) - Remove from builder's
getFakeReportInitializer()return object: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
FakeReportInitializer:status: ReportStatus; - Update in
FakeReportclass:public status: ReportStatus; - Constructor assignment stays the same:
this.status = fakeReportInitialData.status; - Update in
FakeReportBuilderwith appropriate default:private 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
FakeReportInitializer:title: IPropEventBroker<string>; - Update in
FakeReportclass:public title: IPropEventBroker<string>; - Constructor assignment stays the same:
this.title = fakeReportInitialData.title; - Update in
FakeReportBuilder:private 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
/fakes/directory at package root (sibling to/src, NOT inside/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
index.tsalphabetically sorted for maintainability
General Patterns
- All initializer fields are MANDATORY (never use optional
?) - Methods only track return values, not arguments
- Use
getMockingFunctionfor all method implementations - Builder fields should have sensible defaults
- EventBroker fields need two "with" methods
- Always return
thisfrom builder "with" methods for chaining - Protected
getFake[ClassName]Initializer()returns the initializer object - Public
build()creates the fake instance
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
/fakes/index.tsafter updates