Get the FREE Ultimate OpenClaw Setup Guide →

mock-friendly-api-layering

npx machina-cli add skill shimo4228/claude-code-learned-skills/mock-friendly-api-layering --openclaw
Files (1)
SKILL.md
2.0 KB

Mock-Friendly API Layering

Extracted: 2026-02-11 Context: AnkiConnect client (anki_connect.py) — public functions forwarding url to internal _invoke broke mock assertions.

Problem

When public functions forward internal routing parameters (like url, timeout, base_path) to a low-level helper, tests that mock the helper fail on assert_called_once_with() because of unexpected kwargs.

# BAD: ensure_deck forwards url to _invoke
def ensure_deck(name: str, *, url: str = ANKICONNECT_URL) -> None:
    _invoke("createDeck", url=url, deck=name)

# Test expects:
mock.assert_called_once_with("createDeck", deck="pdf2anki::test")
# Actual call:
# _invoke("createDeck", url="http://127.0.0.1:8765", deck="pdf2anki::test")
# → AssertionError: unexpected kwarg url

Solution

Keep internal routing parameters only on the lowest-level function (_invoke). Public functions expose only business-relevant parameters.

# GOOD: public function has no url param
def ensure_deck(name: str) -> None:
    _invoke("createDeck", deck=name)

# _invoke owns the url default internally
def _invoke(action: str, *, url: str = ANKICONNECT_URL, **params: Any) -> Any:
    ...

Design Principle

LayerOwnsExample
_invoke (internal)Transport: url, timeout, headers_invoke("action", url=..., **params)
Public functionsBusiness params onlyensure_deck(name), push_cards(cards, deck_name=...)

When to Use

  • Wrapping HTTP APIs (AnkiConnect, REST, GraphQL) with an internal _invoke / _request helper
  • Any module with a low-level helper + multiple public functions that delegate to it
  • When mock.assert_called_once_with() fails due to extra kwargs from defaults

Source

git clone https://github.com/shimo4228/claude-code-learned-skills/blob/main/skills/mock-friendly-api-layering/SKILL.mdView on GitHub

Overview

Mock-Friendly API Layering addresses failures when tests mock a low-level helper but public functions forward transport params like url or timeout. By keeping transport concerns inside the lowest-level function and exposing only business parameters at public APIs, mock assertions stay stable across changes. This pattern clarifies responsibilities and makes tests deterministic.

How This Skill Works

Place all transport details (url, timeout, headers) in the internal _invoke (or _request) function. Public functions should not accept or forward these internal params; they call the internal helper with only the business data. Tests should assert against the public call signatures, not the transport-layer kwargs.

When to Use It

  • Wrapping HTTP APIs (AnkiConnect, REST, GraphQL) with an internal _invoke or _request helper
  • Any module that has a low-level helper plus multiple public functions delegating to it
  • When mock.assert_called_once_with() fails due to extra kwargs from default transport parameters
  • When you want the public API to expose only business parameters for clarity
  • When you want transport concerns isolated to the internal function across all calls

Quick Start

  1. Step 1: Identify the internal transport helper (_invoke)
  2. Step 2: Move url/timeout/headers into the internal function and remove them from public APIs
  3. Step 3: Refactor public functions to pass business params only and adjust tests

Best Practices

  • Put transport params (url, timeout, headers) on _invoke only
  • Public functions should not declare internal transport params
  • Keep a clear separation of concerns between _invoke and public wrappers
  • Document the expected business params at the public API
  • Update tests to assert on business params, not transport kwargs

Example Use Cases

  • AnkiConnect client (anki_connect.py) public functions forwarding url to _invoke
  • REST API client where a shared _invoke handles base_url and headers
  • GraphQL client that routes through a _request helper
  • Test suites where mock.assert_called_once_with() fails due to extra kwargs
  • Public wrappers like ensure_deck(name) calling _invoke('createDeck', deck=name)

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers