| name | FakeBuilder Generator |
| description | Gera FakeBuilders para agregados DDD usando Chance.js seguindo padrão do projeto com PropOrFactory, type augmentation e dados realistas para testes. |
FakeBuilder Generator Skill
Objetivo
Esta Skill orienta Claude Code a gerar FakeBuilders completos para agregados DDD seguindo o padrão estabelecido no projeto: Chance.js para dados fake realistas, fluent API, type augmentation e suporte a factories.
Quando usar
Ative esta Skill quando:
- Criar um novo agregado que precisa de fake builder para testes
- Gerar dados de teste realistas para testes unitários ou integração
- Implementar test fixtures com Chance.js
- Criar builders com padrão fluent API
- Precisar de dados fake válidos (CPF, CNPJ, emails, etc)
- Configurar test data factories
Entradas esperadas
agregado: Classe do agregado/entidade para o qual gerar o builderpropriedades: Lista de propriedades e seus tiposvalidacoes_especiais: Regras específicas (CPF/CNPJ válido, códigos hierárquicos, etc)caminho_arquivo: Onde salvar o builder (geralmente[modulo]/domain/[nome].fake-builder.ts)
Saídas esperadas
- Arquivo
.fake-builder.tscompleto e funcional - Type augmentation para adicionar método
.fake()no agregado - Comentários explicativos quando necessário
- Exemplos de uso no topo do arquivo
- Suporte a PropOrFactory
para valores fixos ou funções
Estrutura do FakeBuilder
Template Base
import { Chance } from 'chance';
import { [Agregado], Create[Agregado]Props } from './[agregado].aggregate';
type PropOrFactory<T> = T | ((index: number) => T);
export class [Agregado]FakeBuilder<TBuild = any> {
private chance: Chance.Chance;
private countObjs: number;
private baseIndex: number;
private static globalIndex = 0;
// Propriedades com valores padrão usando factories
private _empresaId: PropOrFactory<string> = () => this.chance.guid();
private _propriedade1: PropOrFactory<tipo> = (index: number) => {
// Lógica de geração
};
private constructor(countObjs: number = 1) {
this.countObjs = countObjs;
this.chance = new Chance();
this.baseIndex = [Agregado]FakeBuilder.globalIndex * 100;
[Agregado]FakeBuilder.globalIndex += 1;
}
static anEntity() {
return new [Agregado]FakeBuilder<[Agregado]>(1);
}
static theEntities(countObjs: number) {
return new [Agregado]FakeBuilder<[Agregado][]>(countObjs);
}
with[Propriedade](valueOrFactory: PropOrFactory<tipo>) {
this._propriedade = valueOrFactory;
return this;
}
build(): TBuild {
const entities = new Array(this.countObjs)
.fill(undefined)
.map((_, index) => {
const props: Create[Agregado]Props = {
empresaId: this.callFactory(this._empresaId, index),
propriedade1: this.callFactory(this._propriedade1, index),
// ... outras propriedades
};
const entity = [Agregado].create(props);
return entity;
});
return this.countObjs === 1 ? (entities[0] as any) : (entities as any);
}
private callFactory(factoryOrValue: PropOrFactory<any>, index: number) {
return typeof factoryOrValue === 'function'
? factoryOrValue(index)
: factoryOrValue;
}
}
// Adicionar método estático ao agregado
[Agregado].fake = function () {
return [Agregado]FakeBuilder;
};
// Type augmentation
declare module './[agregado].aggregate' {
export interface [Agregado] {
fake?: typeof [Agregado]FakeBuilder;
}
namespace [Agregado] {
export let fake: () => typeof [Agregado]FakeBuilder;
}
}
Padrões Específicos por Tipo de Dado
1. Códigos Simples (3 dígitos)
Para bancos:
private _codigo: PropOrFactory<string> = (index: number) => {
const value = (this.baseIndex + index) % 900; // 0-899
return (value + 100).toString().padStart(3, '0'); // 100-999
};
2. Códigos Sequenciais
Para centro de custo:
private _codigo: PropOrFactory<string> = (index: number) =>
`CC${(this.baseIndex + index + 1).toString().padStart(3, '0')}`;
3. Códigos Hierárquicos
Para plano de contas:
private _codigo: PropOrFactory<string> = (index: number) => {
const level1 = ((this.baseIndex + index) % 9) + 1;
const level2 = ((this.baseIndex + index) % 99) + 1;
const level3 = (index % 999) + 1;
return `${level1}.${level2.toString().padStart(2, '0')}.${level3.toString().padStart(3, '0')}`;
};
4. CPF/CNPJ Válidos
Para clientes/fornecedores:
private _documento: PropOrFactory<string> = (index: number) => {
return this._tipoPessoa === 'PF'
? this.generateValidCPF()
: this.generateValidCNPJ();
};
private generateValidCPF(): string {
const digits = Array.from({ length: 9 }, () =>
this.chance.integer({ min: 0, max: 9 })
);
const d1 = this.calculateCPFDigit(digits, 10);
const d2 = this.calculateCPFDigit([...digits, d1], 11);
return [...digits, d1, d2].join('');
}
private calculateCPFDigit(digits: number[], weight: number): number {
const sum = digits.reduce((acc, digit, idx) => {
return acc + digit * (weight - idx);
}, 0);
const remainder = sum % 11;
return remainder < 2 ? 0 : 11 - remainder;
}
private generateValidCNPJ(): string {
const digits = Array.from({ length: 12 }, () =>
this.chance.integer({ min: 0, max: 9 })
);
const d1 = this.calculateCNPJDigit(digits, [5,4,3,2,9,8,7,6,5,4,3,2]);
const d2 = this.calculateCNPJDigit([...digits, d1], [6,5,4,3,2,9,8,7,6,5,4,3,2]);
return [...digits, d1, d2].join('');
}
private calculateCNPJDigit(digits: number[], weights: number[]): number {
const sum = digits.reduce((acc, digit, idx) => {
return acc + digit * weights[idx];
}, 0);
const remainder = sum % 11;
return remainder < 2 ? 0 : 11 - remainder;
}
5. Nomes Realistas
Usando Chance.js:
private _nome: PropOrFactory<string> = (index: number) =>
this.chance.name();
// Para empresas
private _razaoSocial: PropOrFactory<string> = (index: number) =>
`${this.chance.company()} ${this.chance.pickone(['Ltda', 'S.A.', 'ME', 'EIRELI'])}`;
6. Emails
private _email: PropOrFactory<string | null> = (index: number) =>
this.chance.email();
// Ou null por padrão se opcional
private _email: PropOrFactory<string | null> = () => null;
7. Telefones
private _telefone: PropOrFactory<string | null> = (index: number) =>
`11${this.chance.integer({ min: 900000000, max: 999999999 })}`;
8. Enums
private _tipo: PropOrFactory<TipoPlanoContas> = (index: number) =>
this.chance.pickone(['RECEITA', 'DESPESA']);
private _status: PropOrFactory<Status> = () => 'ATIVO';
9. Campos Opcionais
private _descricao: PropOrFactory<string | null> = () => null;
// Ou com valor gerado
private _descricao: PropOrFactory<string | null> = (index: number) =>
this.chance.sentence({ words: 10 });
10. Relacionamentos (IDs)
private _parentId: PropOrFactory<string | null> = () => null;
// Para forçar relacionamento
withParentId(valueOrFactory: PropOrFactory<string>) {
this._parentId = valueOrFactory;
return this;
}
Chance.js - Métodos Úteis
// Identificadores
this.chance.guid() // UUID
this.chance.hash({ length: 10 }) // Hash aleatório
// Textos
this.chance.name() // Nome de pessoa
this.chance.company() // Nome de empresa
this.chance.email() // Email
this.chance.sentence({ words: 10 }) // Frase
this.chance.paragraph() // Parágrafo
// Números
this.chance.integer({ min: 0, max: 999 })
this.chance.floating({ min: 0, max: 100, fixed: 2 })
// Datas
this.chance.date()
this.chance.timestamp()
// Endereços
this.chance.address()
this.chance.city()
this.chance.state()
this.chance.zip()
this.chance.country()
// Telefones
this.chance.phone()
// Seleção
this.chance.pickone(['A', 'B', 'C'])
this.chance.shuffle(['A', 'B', 'C'])
// Booleanos
this.chance.bool()
this.chance.bool({ likelihood: 70 }) // 70% true
Exemplos Completos
Exemplo 1: Banco (Simples)
import { Chance } from 'chance';
import { Banco, CreateBancoProps } from './banco.aggregate';
type PropOrFactory<T> = T | ((index: number) => T);
export class BancoFakeBuilder<TBuild = any> {
private chance: Chance.Chance;
private countObjs: number;
private baseIndex: number;
private static globalIndex = 0;
private _empresaId: PropOrFactory<string> = () => this.chance.guid();
private _codigo: PropOrFactory<string> = (index: number) => {
const value = (this.baseIndex + index) % 900;
return (value + 100).toString().padStart(3, '0');
};
private _nome: PropOrFactory<string> = (index: number) =>
`Banco ${this.baseIndex + index + 1}`;
private constructor(countObjs: number = 1) {
this.countObjs = countObjs;
this.chance = new Chance();
this.baseIndex = BancoFakeBuilder.globalIndex * 100;
BancoFakeBuilder.globalIndex += 1;
}
static anEntity() {
return new BancoFakeBuilder<Banco>(1);
}
static theEntities(countObjs: number) {
return new BancoFakeBuilder<Banco[]>(countObjs);
}
withEmpresaId(valueOrFactory: PropOrFactory<string>) {
this._empresaId = valueOrFactory;
return this;
}
withCodigo(valueOrFactory: PropOrFactory<string>) {
this._codigo = valueOrFactory;
return this;
}
withNome(valueOrFactory: PropOrFactory<string>) {
this._nome = valueOrFactory;
return this;
}
build(): TBuild {
const entities = new Array(this.countObjs)
.fill(undefined)
.map((_, index) => {
const props: CreateBancoProps = {
empresaId: this.callFactory(this._empresaId, index),
codigo: this.callFactory(this._codigo, index),
nome: this.callFactory(this._nome, index),
};
const banco = Banco.create(props);
return banco;
});
return this.countObjs === 1 ? (entities[0] as any) : (entities as any);
}
private callFactory(factoryOrValue: PropOrFactory<any>, index: number) {
return typeof factoryOrValue === 'function'
? factoryOrValue(index)
: factoryOrValue;
}
}
Banco.fake = function () {
return BancoFakeBuilder;
};
declare module './banco.aggregate' {
export interface Banco {
fake?: typeof BancoFakeBuilder;
}
namespace Banco {
export let fake: () => typeof BancoFakeBuilder;
}
}
Exemplo 2: PlanoContas (Hierárquico)
Uso dos FakeBuilders
Single Entity
const banco = Banco.fake()
.anEntity()
.build();
Multiple Entities
const bancos = Banco.fake()
.theEntities(5)
.build();
Custom Properties
const banco = Banco.fake()
.anEntity()
.withCodigo('001')
.withNome('Banco do Brasil')
.build();
Factory Functions
const bancos = Banco.fake()
.theEntities(10)
.withCodigo((index) => (index + 1).toString().padStart(3, '0'))
.withEmpresaId(() => 'same-empresa-id')
.build();
Test Data Setup
describe('BancoService', () => {
let empresaId: string;
let bancos: Banco[];
beforeEach(() => {
empresaId = 'test-empresa-id';
bancos = Banco.fake()
.theEntities(5)
.withEmpresaId(empresaId)
.build();
});
it('should have all bancos with same empresaId', () => {
expect(bancos.every(b => b.empresaId === empresaId)).toBe(true);
});
});
Checklist de Implementação
Ao gerar um FakeBuilder, SEMPRE inclua:
Estrutura Base
- Import de Chance
- Import do agregado e suas props
- Type
PropOrFactory<T> - Classe com TBuild generic
- Propriedades privadas com factories
- Constructor privado
- Métodos estáticos
anEntity()etheEntities() - Métodos
withX()para cada propriedade - Método
build() - Método auxiliar
callFactory()
Type Augmentation
- Adicionar método
.fake()ao agregado - Declare module correto
- Export do tipo correto
Dados Realistas
- Usar Chance.js apropriadamente
- CPF/CNPJ válidos quando necessário
- Códigos únicos usando baseIndex
- Emails válidos
- Nomes em português quando apropriado
Índice Global
- Static globalIndex para evitar colisões
- baseIndex calculado no constructor
- Incremento do globalIndex
Quando NÃO usar esta Skill
- Para criar in-memory repositories
- Para refatorações de código existente