Loading...
Loading...
Guide for implementing James Shore's Nullables pattern and A-Frame architecture for testing without mocks. Use when implementing or refactoring code to follow patterns of: (1) Separating logic, infrastructure, and application layers, (2) Creating testable infrastructure with create/createNull factory methods, (3) Writing narrow, sociable, state-based tests without mocks, (4) Implementing value objects, (5) Building infrastructure wrappers that use embedded stubs, or (6) Designing dependency injection through static factory methods.
npx skill4agent add danielbush/skills nullables.create().createNull().create.createNull.createclass ServiceClass {
static create(config) {
// Production instance - calls .create() on dependencies
const dependency = Dependency.create();
return new ServiceClass(dependency);
}
static createNull(config = {}) {
// Test instance - calls .createNull() on dependencies
const dependency = Dependency.createNull();
return new ServiceClass(dependency);
}
constructor(dependency) {
// Constructor receives instantiated dependencies
}
}.createNull().create()Service.createNull({ response: data })service.trackSentEmails()FooFoofooBarClientFoo.createFoo.createNullFoo.createNullFooFoofooFoo.create,Bar.createFoo.createFoo.createFoofooFooFoo.createFoo.createNullValValVal.createVal.createNullVal.createVal.createTestInstance.createTestInstanceFooFooBarFooFooFoo.createBar.createFoocreateBarBarFooBarBarFoo'sBar.createBarFooFoo.createNullFoo.createNullBar.createNullFoocreateBarcreateBarBar.createNull.createNullfetch.create.createNullClientHttpClientDiskClientClientClientClientClient.createNullClient.createNull.createNullClient.createNullClientneverthrow.type.causeValClientFooClientFoo.createFoo.createClientFoo.create.create.createNullFooFooFoo.createNullClientClient.createNullClient.createfoofooClient.createNullClientClientEventEmitter.emit.on.offXXClienttrackXX.trackXtrackerTrackerTrackerClientTrackerXtracker.stop()Trackertracker.clear()tracker.dataTrackerstatic create()static createNull().create().create().createNull().createNull().createNull()Service.createNull({ response: data })service.trackOutput().create().create().createNull().createNull()class HttpClient {
static create() {
return new HttpClient(realHttp); // Real HTTP library
}
static createNull() {
return new HttpClient(new StubbedHttp()); // Embedded stub
}
constructor(http) {
this._http = http;
}
async request(options) {
return await this._http.request(options);
}
}
// Embedded stub - mimics third-party library interface
class StubbedHttp {
async request(options) {
return { status: 200, body: {} };
}
}async transfer(fromId, toId, amount) {
// Read from infrastructure
const accounts = await this._repo.getAccounts(fromId, toId);
// Process with logic
const result = this._logic.transfer(accounts, amount);
// Write through infrastructure
await this._repo.saveAccounts(result);
}class Account extends EventEmitter {
deposit(amount) {
this._balance += amount;
this.emit('balance-changed', { balance: this._balance });
}
}
// Test by observing events
account.on('balance-changed', (event) => events.push(event));