From 9cbbb045b9119de5142cf1937c73097f612b0b5d Mon Sep 17 00:00:00 2001 From: Kevin Sivic Date: Sun, 30 Nov 2025 22:51:32 -0500 Subject: [PATCH] Add A-Frame architecture and Testing Without Mocks guidelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document James Shore's patterns for clean architecture: - A-Frame: Logic and Infrastructure as independent peers - Testing Without Mocks: state-based tests, Nullables, no mocking - Testing approach by layer (Logic, Infrastructure, Application) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- AGENTS.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 0fa4017..42367e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,56 @@ A pre-commit hook is provided that runs `mix format --check-formatted` and `mix - **Prefer small, focused commits** - each commit should represent a single logical change - **Write tests first** - follow test-driven development (TDD) where practical; write failing tests before implementing features - Use `mix precommit` alias when you are done with all changes and fix any pending issues + +### Architecture: A-Frame Pattern + +This project follows [James Shore's A-Frame Architecture](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks) where Logic and Infrastructure are independent peers coordinated at the Application layer: + +``` + Application/UI Values + / \ + V V +Logic Infrastructure +``` + +- **Logic** - Pure business logic with no external dependencies; easily testable with state-based tests +- **Infrastructure** - Wrappers around external systems (database, APIs, file system); one wrapper per external system +- **Application** - Coordinates Logic and Infrastructure; thin layer that wires things together +- **Values** - Immutable data structures passed between layers + +### Testing: Without Mocks + +Follow the "Testing Without Mocks" pattern language. Key principles: + +- **Avoid mocks** - They test implementation details and break when refactoring +- **State-based tests** - Check output/state, not function calls +- **Narrow tests** - Focus on specific functions/behaviors, not system-wide functionality +- **Overlapping sociable tests** - Execute real dependencies; dependencies have their own thorough tests + +#### Nullables Pattern + +For infrastructure code, implement **Nullables** - production code with an off-switch for external communication: + +- Create a factory method that returns a "null" version for testing (disables external calls but preserves behavior) +- Nullables have legitimate production uses (dry-run modes, cache warming) +- Test Nullables like any production code + +#### Infrastructure Wrappers + +- One wrapper module per external system (Repo for database, HTTP clients for APIs) +- Expose clean interfaces matching application needs, not third-party APIs +- These are the **only** places where external communication occurs +- Test with narrow integration tests against real systems (test database, etc.) + +#### Testing Approach by Layer + +- **Logic** - Pure state-based tests; no infrastructure needed +- **Infrastructure wrappers** - Narrow integration tests with real external systems +- **Application code** - Sociable tests with Nullable dependencies +- **LiveViews/Controllers** - Use Phoenix test helpers with test database + +### Dependencies + - Use the already included and available `:req` (`Req`) library for HTTP requests, **avoid** `:httpoison`, `:tesla`, and `:httpc`. Req is included by default and is the preferred HTTP client for Phoenix apps ### Phoenix v1.8 guidelines