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