Claude-skill-registry FakeBuilder Generator
Gera FakeBuilders para agregados DDD usando Chance.js seguindo padrão do projeto com PropOrFactory, type augmentation e dados realistas para testes.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fakebuilder-generator" ~/.claude/skills/majiayu000-claude-skill-registry-fakebuilder-generator && rm -rf "$T"
manifest:
skills/data/fakebuilder-generator/SKILL.mdsource content
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
: Classe do agregado/entidade para o qual gerar o builderagregado
: Lista de propriedades e seus tipospropriedades
: Regras específicas (CPF/CNPJ válido, códigos hierárquicos, etc)validacoes_especiais
: Onde salvar o builder (geralmentecaminho_arquivo
)[modulo]/domain/[nome].fake-builder.ts
Saídas esperadas
- Arquivo
completo e funcional.fake-builder.ts - Type augmentation para adicionar método
no agregado.fake() - Comentários explicativos quando necessário
- Exemplos de uso no topo do arquivo
- Suporte a PropOrFactory<T> 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
eanEntity()theEntities() - Métodos
para cada propriedadewithX() - Método
build() - Método auxiliar
callFactory()
Type Augmentation
- Adicionar método
ao agregado.fake() - 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