{"id":"ee36c745-f9e4-49e0-a0d8-e974221e8114","shortId":"MmX7dG","kind":"skill","title":"oracle-testing","tagline":"Stub-Driven TDD and layer boundary testing. Use when writing tests, deciding what to test, or testing at component boundaries.","description":"# Testing Skill\n\nStub-Driven Test-Driven Development and layer boundary testing for functional core and effectful edge architecture.\n\n## Core Principle: Stub-Driven TDD\n\nTest-Driven Development workflow for the functional core / effectful edge pattern:\n\n```\n1. Stub   → Create minimal interface/function signatures\n2. Test   → Write tests against stubs\n3. Implement → Make tests pass with real implementation\n4. Refactor  → Improve code while keeping tests green\n```\n\n**Key insight:** Write interface signatures first, test against those, then implement—not the other way around.\n\nSee [references/stub-driven-tdd.md] for complete workflow examples.\n\n## Layer Boundary Testing\n\nTest at the boundaries between functional core and effectful edge, not internal implementation.\n\n```\nTest here ──────▼──────────────────▼────── Test here\n          Effectful Edge    │    Functional Core\n              (stub)        │       (unit test)\n```\n\n### Where to Test Each Layer\n\n| Layer | Test Type | What to Stub | What to Assert |\n|-------|-----------|--------------|-----------------|\n| **Entity** | Unit | Nothing (pure) | Validation, rules, transforms |\n| **Service** | Unit | Repositories | Orchestration logic, error handling |\n| **Router** | Integration | Service | Status codes, response format |\n| **Repository** | Integration | DB connection | CRUD operations, queries |\n| **Consumer** | Integration | Service | Event parsing, service calls |\n\nSee [references/boundaries.md] for detailed testing patterns by layer.\n\n## Functional Core Testing\n\n### Entity Tests (Pure Functions)\n\nFocus: Validation, business rules, data transformations\n\n```typescript\ndescribe('Order entity', () => {\n  describe('validation', () => {\n    it('rejects empty items', () => {\n      const order = new Order('1', 'C1', [], 'pending', 0);\n      expect(order.validate().ok).toBe(false);\n    });\n  });\n\n  describe('business rules', () => {\n    it('prevents cancelling shipped order', () => {\n      const order = new Order('1', 'C1', [], 'shipped', 0);\n      expect(order.canCancel()).toBe(false);\n    });\n  });\n\n  describe('transformations', () => {\n    it('converts request to entity with calculated total', () => {\n      const order = Order.fromRequest({\n        customerId: 'C1',\n        items: [\n          { productId: 'P1', quantity: 2, price: 10 },\n          { productId: 'P2', quantity: 1, price: 15 }\n        ]\n      });\n      expect(order.total).toBe(35);\n    });\n  });\n});\n```\n\n### Service Tests (Stubbed Dependencies)\n\nFocus: Orchestration logic with stubbed repositories\n\n```typescript\ndescribe('OrderService.createOrder', () => {\n  let service: OrderService;\n  let mockRepo: OrderRepository;\n\n  beforeEach(() => {\n    mockRepo = {\n      save: vi.fn().mockResolvedValue({ id: '123' }),\n      findById: vi.fn()\n    };\n    service = new OrderService(mockRepo);\n  });\n\n  it('creates order with valid data', async () => {\n    const result = await service.createOrder({\n      customerId: 'C1',\n      items: [{ productId: 'P1', quantity: 2 }]\n    });\n\n    expect(result.ok).toBe(true);\n    expect(mockRepo.save).toHaveBeenCalled();\n  });\n\n  it('does not save when validation fails', async () => {\n    const result = await service.createOrder({\n      customerId: 'C1',\n      items: [] // Invalid\n    });\n\n    expect(result.ok).toBe(false);\n    expect(mockRepo.save).not.toHaveBeenCalled();\n  });\n});\n```\n\nSee [references/core-testing.md] for comprehensive Entity and Service examples.\n\n## Effectful Edge Testing\n\n### Router, Repository, Consumer Integration Tests\n\nFocus: Real HTTP/database/events with stubbed core\n\n```typescript\n// Router: real HTTP, stub service\ndescribe('POST /orders', () => {\n  it('returns 201 for valid request', async () => {\n    const mockService = {\n      createOrder: vi.fn().mockResolvedValue(Ok({ id: '123' }))\n    };\n    const app = createApp(mockService);\n\n    const response = await request(app)\n      .post('/orders')\n      .send({ customerId: 'C1', items: [{ productId: 'P1', quantity: 2 }] });\n\n    expect(response.status).toBe(201);\n  });\n});\n\n// Repository: real test database\ndescribe('OrderRepository.save', () => {\n  it('persists order to database', async () => {\n    const repo = new OrderRepository(testDb);\n    const saved = await repo.save({\n      id: '123',\n      customer_id: 'C1',\n      items: '[]',\n      status: 'pending',\n      total: 0\n    });\n\n    const found = await testDb.orders.findOne({ id: '123' });\n    expect(found).toBeDefined();\n  });\n});\n\n// Consumer: real events, stub service\ndescribe('OrderConsumer', () => {\n  it('handles OrderPlaced event', async () => {\n    const mockService = {\n      processOrder: vi.fn().mockResolvedValue(Ok({}))\n    };\n    const consumer = new OrderConsumer(mockService);\n\n    await consumer.handle({\n      type: 'OrderPlaced',\n      data: { orderId: '123' }\n    });\n\n    expect(mockService.processOrder).toHaveBeenCalledWith('123');\n  });\n});\n```\n\nSee [references/edge-testing.md] for Router, Repository, Consumer, Producer, and Client patterns.\n\n## Test Coverage Guidelines\n\nAim for strategic coverage, not 100%:\n\n**High Coverage (Critical):**\n- Entity validation and business rules\n- Service orchestration logic\n- Critical user journeys (integration tests)\n- Data transformations with logic\n\n**Medium Coverage (Important):**\n- Error handling paths\n- Edge cases in business logic\n- API contract validation\n\n**Low Coverage (Optional):**\n- Simple getters/setters\n- Framework boilerplate\n- Trivial mappings\n- Internal utilities\n\n## What NOT to Test\n\nAvoid testing implementation details, framework behavior, and trivial code:\n\n- Don't test private methods (test through public API)\n- Don't test simple getters/setters (no logic = no test value)\n- Don't test framework behavior (Express, database driver already tested)\n- Don't test third-party library behavior (lodash, validation libraries)\n- Don't test trivial mappings without logic\n\nSee [references/anti-patterns.md] for anti-patterns with examples and fixes.\n\n## Testing → Implementation Flow\n\nFollow this dependency order:\n\n```\n1. Entity tests    (pure functions, fast)\n2. Service tests   (stubbed dependencies, fast)\n3. Integration tests (real IO, slower)\n```\n\nThis enables TDD: write tests first at lower layers, then implement, then build upward.\n\n## Quick Reference\n\n**For Entity Testing:** See [references/core-testing.md]\n**For Service Testing:** See [references/core-testing.md]\n**For Router/Repo/Consumer:** See [references/edge-testing.md]\n**For Workflow Examples:** See [references/stub-driven-tdd.md]\n**For What NOT to Do:** See [references/anti-patterns.md]","tags":["oracle","testing","atelier","martinffx","agent-skills","agentic-coding","anthropic","claude-code","claude-skills","code-review","codex","codex-skill"],"capabilities":["skill","source-martinffx","skill-oracle-testing","topic-agent-skills","topic-agentic-coding","topic-anthropic","topic-claude-code","topic-claude-skills","topic-code-review","topic-codex","topic-codex-skill","topic-opencode","topic-prompt-engineering","topic-sdd","topic-spec-driven-development"],"categories":["atelier"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/martinffx/atelier/oracle-testing","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add martinffx/atelier","source_repo":"https://github.com/martinffx/atelier","install_from":"skills.sh"}},"qualityScore":"0.461","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 23 github stars · SKILL.md body (6,589 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:05:22.927Z","embedding":null,"createdAt":"2026-05-10T07:03:11.790Z","updatedAt":"2026-05-18T19:05:22.927Z","lastSeenAt":"2026-05-18T19:05:22.927Z","tsv":"'/orders':395,421 '0':227,248,464 '1':63,224,245,278,649 '10':274 '100':526 '123':310,410,456,470,503,507 '15':280 '2':69,272,334,429,655 '201':398,433 '3':75,661 '35':284 '4':83 'aim':521 'alreadi':612 'anti':636 'anti-pattern':635 'api':558,593 'app':412,419 'architectur':44 'around':106 'assert':153 'async':323,349,402,445,485 'avoid':576 'await':326,352,417,453,467,497 'beforeeach':304 'behavior':581,608,621 'boilerpl':567 'boundari':10,24,36,114,119 'build':679 'busi':206,234,533,556 'c1':225,246,267,329,355,424,459 'calcul':261 'call':188 'cancel':238 'case':554 'client':516 'code':86,172,584 'complet':110 'compon':23 'comprehens':368 'connect':178 'const':220,241,263,324,350,403,411,415,446,451,465,486,492 'consum':182,378,474,493,513 'consumer.handle':498 'contract':559 'convert':256 'core':40,45,59,122,136,198,386 'coverag':519,524,528,548,562 'creat':65,318 'createapp':413 'createord':405 'critic':529,538 'crud':179 'custom':457 'customerid':266,328,354,423 'data':208,322,501,543 'databas':437,444,610 'db':177 'decid':16 'depend':288,647,659 'describ':211,214,233,253,296,393,438,479 'detail':192,579 'develop':33,54 'driven':6,29,32,49,53 'driver':611 'edg':43,61,125,134,374,553 'effect':42,60,124,133,373 'empti':218 'enabl':668 'entiti':154,200,213,259,369,530,650,684 'error':166,550 'event':185,476,484 'exampl':112,372,639,699 'expect':228,249,281,335,339,358,362,430,471,504 'express':609 'fail':348 'fals':232,252,361 'fast':654,660 'findbyid':311 'first':96,672 'fix':641 'flow':644 'focus':204,289,381 'follow':645 'format':174 'found':466,472 'framework':566,580,607 'function':39,58,121,135,197,203,653 'getters/setters':565,598 'green':90 'guidelin':520 'handl':167,482,551 'high':527 'http':390 'http/database/events':383 'id':309,409,455,458,469 'implement':76,82,101,128,578,643,677 'import':549 'improv':85 'insight':92 'integr':169,176,183,379,541,662 'interfac':94 'interface/function':67 'intern':127,570 'invalid':357 'io':665 'item':219,268,330,356,425,460 'journey':540 'keep':88 'key':91 'layer':9,35,113,144,145,196,675 'let':298,301 'librari':620,624 'lodash':622 'logic':165,291,537,546,557,600,631 'low':561 'lower':674 'make':77 'map':569,629 'medium':547 'method':589 'minim':66 'mockrepo':302,305,316 'mockrepo.save':340,363 'mockresolvedvalu':308,407,490 'mockservic':404,414,487,496 'mockservice.processorder':505 'new':222,243,314,448,494 'not.tohavebeencalled':364 'noth':156 'ok':230,408,491 'oper':180 'option':563 'oracl':2 'oracle-test':1 'orchestr':164,290,536 'order':212,221,223,240,242,244,264,319,442,648 'order.cancancel':250 'order.fromrequest':265 'order.total':282 'order.validate':229 'orderconsum':480,495 'orderid':502 'orderplac':483,500 'orderrepositori':303,449 'orderrepository.save':439 'orderservic':300,315 'orderservice.createorder':297 'p1':270,332,427 'p2':276 'pars':186 'parti':619 'pass':79 'path':552 'pattern':62,194,517,637 'pend':226,462 'persist':441 'post':394,420 'prevent':237 'price':273,279 'principl':46 'privat':588 'processord':488 'produc':514 'productid':269,275,331,426 'public':592 'pure':157,202,652 'quantiti':271,277,333,428 'queri':181 'quick':681 'real':81,382,389,435,475,664 'refactor':84 'refer':682 'references/anti-patterns.md':633,708 'references/boundaries.md':190 'references/core-testing.md':366,687,692 'references/edge-testing.md':509,696 'references/stub-driven-tdd.md':108,701 'reject':217 'repo':447 'repo.save':454 'repositori':163,175,294,377,434,512 'request':257,401,418 'respons':173,416 'response.status':431 'result':325,351 'result.ok':336,359 'return':397 'router':168,376,388,511 'router/repo/consumer':694 'rule':159,207,235,534 'save':306,345,452 'see':107,189,365,508,632,686,691,695,700,707 'send':422 'servic':161,170,184,187,285,299,313,371,392,478,535,656,689 'service.createorder':327,353 'ship':239,247 'signatur':68,95 'simpl':564,597 'skill':26 'skill-oracle-testing' 'slower':666 'source-martinffx' 'status':171,461 'strateg':523 'stub':5,28,48,64,74,137,150,287,293,385,391,477,658 'stub-driven':4,27,47 'tdd':7,50,669 'test':3,11,15,19,21,25,31,37,52,70,72,78,89,97,115,116,129,131,139,142,146,193,199,201,286,375,380,436,518,542,575,577,587,590,596,602,606,613,616,627,642,651,657,663,671,685,690 'test-driven':30,51 'testdb':450 'testdb.orders.findone':468 'third':618 'third-parti':617 'tobe':231,251,283,337,360,432 'tobedefin':473 'tohavebeencal':341 'tohavebeencalledwith':506 'topic-agent-skills' 'topic-agentic-coding' 'topic-anthropic' 'topic-claude-code' 'topic-claude-skills' 'topic-code-review' 'topic-codex' 'topic-codex-skill' 'topic-opencode' 'topic-prompt-engineering' 'topic-sdd' 'topic-spec-driven-development' 'total':262,463 'transform':160,209,254,544 'trivial':568,583,628 'true':338 'type':147,499 'typescript':210,295,387 'unit':138,155,162 'upward':680 'use':12 'user':539 'util':571 'valid':158,205,215,321,347,400,531,560,623 'valu':603 'vi.fn':307,312,406,489 'way':105 'without':630 'workflow':55,111,698 'write':14,71,93,670","prices":[{"id":"b8fe34e6-4577-458d-a102-1d6eff21df1d","listingId":"ee36c745-f9e4-49e0-a0d8-e974221e8114","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"martinffx","category":"atelier","install_from":"skills.sh"},"createdAt":"2026-05-10T07:03:11.790Z"}],"sources":[{"listingId":"ee36c745-f9e4-49e0-a0d8-e974221e8114","source":"github","sourceId":"martinffx/atelier/oracle-testing","sourceUrl":"https://github.com/martinffx/atelier/tree/main/skills/oracle-testing","isPrimary":false,"firstSeenAt":"2026-05-10T07:03:11.790Z","lastSeenAt":"2026-05-18T19:05:22.927Z"}],"details":{"listingId":"ee36c745-f9e4-49e0-a0d8-e974221e8114","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"martinffx","slug":"oracle-testing","github":{"repo":"martinffx/atelier","stars":23,"topics":["agent-skills","agentic-coding","anthropic","claude-code","claude-skills","code-review","codex","codex-skill","opencode","prompt-engineering","sdd","spec-driven-development"],"license":"mit","html_url":"https://github.com/martinffx/atelier","pushed_at":"2026-05-18T06:56:45Z","description":"An atelier for Opencode, Claude Code, and other coding agents: spec-driven workflows, deep thinking, and code quality.","skill_md_sha":"a37e2bc3bea248436e9f857cbf956409c3e370a5","skill_md_path":"skills/oracle-testing/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/martinffx/atelier/tree/main/skills/oracle-testing"},"layout":"multi","source":"github","category":"atelier","frontmatter":{"name":"oracle-testing","description":"Stub-Driven TDD and layer boundary testing. Use when writing tests, deciding what to test, or testing at component boundaries."},"skills_sh_url":"https://skills.sh/martinffx/atelier/oracle-testing"},"updatedAt":"2026-05-18T19:05:22.927Z"}}