SwiftMocking
SkillSearchUse when writing Swift tests with the SwiftMocking library — creating @Mockable mocks, stubbing with when/verify, matching arguments, class-constrained protocols (@Mockable([.composition])), or hand-writing mocks (protocol inheritance chains, mocking classes).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the SwiftMocking skill
What this skill tells your AI
The instructions your AI receives, as published by danielcardonarojas/swift-mocking in skills/swift-mocking/SKILL.md and read by ahel’s review.
Mocking for Swift protocols: @Mockable generates mock classes; when(...) stubs, verify(...) asserts calls. When the macro can't generate a mock — most notably protocol inheritance (protocol B: A — the macro drops inherited requirements and the mock fails to conform) — hand-write the mock following the exact generated-code shape.
The macro is not the only entry point. Two escape hatches cover everything it can't reach, and both are fully supported (see spies-and-composition.md):
Spyused directly — a standalone recorder needing noMocksubclass and no macro. This is how you mock classes, which@Mockablecannot touch at all: subclass the class and back each override with aSpy.- Composition over inheritance — hold a
let mock = Mock()property instead of inheriting fromMock.Mock's@dynamicMemberLookupsubscript ispublic, somock.nameresolves spies from outside the class. Required when the type already has a superclass (UIViewController, a legacy base class) or isn't a class at all (struct, actor). Substitutesuper.name→self.mock.nameandadapt(...)→Mock.adapt(...); everything else in manual-mocking.md is unchanged.
The macro generates the composed form itself with @Mockable([.composition]) — no hand-writing — when the blocker is a protocol whose conformers must inherit a class:
@Mockable([.composition])
protocol ViewControllerService: SampleBase { ... }
Hand-write composition only for what the macro still can't express: structs, actors, and mocking a concrete class.
When to use what
| Situation | Read |
|---|---|
| Protocol has no inheritance; need a mock | @Mockable protocol P {...} → use PMock() |
| Protocol inherits another protocol with members | manual-mocking.md (macro cannot do this) |
Protocol constrained to a class (protocol P: SomeClass) | @Mockable([.composition]) — the macro handles it |
| Mocking a class (no protocol available) | spies-and-composition.md — subclass + raw Spy |
Can't inherit Mock (struct, actor, or a hand-written type with a superclass) | spies-and-composition.md — let mock = Mock() |
| Mocking without any protocol (closure/TCA dependencies) | usage.md / spies-and-composition.md — Spy + adapt |
Stubbing/verifying settable properties or subscripts ({ get set }) | usage.md — Properties & subscripts; hand-written shape in manual-mocking.md |
| Need mock source without the macro (plugin unavailable, codegen, review) | mockable CLI (below) when available; hand-writing per manual-mocking.md is always valid |
@Sendable/Swift 6 concurrency errors when stubbing | sendable.md |
| Stubbing / matching / verifying API reference | usage.md |
| Exact signature of a public API (overloads, constraints, defaults) | references/interface/ (below) |
API interface
Machine-generated from the compiled modules — the authoritative signature reference. Check these before guessing at an overload or a generic constraint:
- Core library:
references/interface/SwiftMocking.swiftinterface - XCTest/swift-testing helpers:
references/interface/SwiftMockingTestSupport.swiftinterface
One caveat: Sendable is a marker protocol and the compiler elides it from
where clauses in emitted interfaces — thenThrow<E: Error & Sendable> prints
as where E : Error. The constraint is still enforced. For Sendable questions
trust sendable.md, not these files.
Deterministic generation: the mockable CLI
When a mock must exist as written source (macro plugin unavailable, codegen pipeline, review), the mockable CLI is the fastest exact path. Hand-writing per manual-mocking.md is equally correct and needs nothing; use the CLI when it's available, hand-write when it isn't or when you're already customizing.
echo 'protocol P { func price(_ item: String) throws -> Int }' | mockable
Invoke as mockable when it is on PATH; otherwise .build/release/mockable inside a swift-mocking checkout (build once with swift build -c release --product mockable, then optionally copy the binary onto PATH).
- Input needs no annotation — paste the protocol as it is written in the source you're mocking.
- Pass options with
--options, comma-separated (--options composition,suffixMock). Use it for protocols you can't or shouldn't annotate — most importantly--options compositionfor a protocol constrained to a class, the fastest way to see the composed shape:echo 'protocol P: UIViewController { func load() }' | mockable --options composition - Options may still ride the input as
@Mockable([.suffixMock]) protocol P {...}, which wins over--options. Prefer the flag: it keeps the stdin text identical to the real declaration. - Default output keeps the macro's
#if DEBUGwrapper; pass--no-debug-wrapwhen pasting into a test target (DEBUG is per build configuration — a wrapped mock vanishes underswift test -c release). - A stderr warning about inherited requirements means the output will not conform — hand-write per manual-mocking.md instead.
- Output keeps the macro's zero-arg/property-getter shape:
when(...)silently stubs a disconnected spy, andverify(...)reports zero calls for those members — apply the pinned-spy fix from manual-mocking.md before relying on them.
The one rule for manual mocks
Every protocol requirement gets two members in the mock class:
- Runtime member — fulfills the protocol, forwards to the spy:
adapt(super.method, args) - Interaction member — same name,
ArgMatcher<T>parameters, returnsInteraction<Inputs..., Effect, Output>— whatwhen(...)/verify(...)consume
class FooMock: Mock, @unchecked Sendable, Foo {
func price(_ item: String) throws -> Int {
return try adaptThrowing(super.price, item)
}
func price(_ item: ArgMatcher<String>) -> Interaction<String, Throws, Int> {
Interaction(item, spy: super.price)
}
}
Class shell: inherit Mock first, restate @unchecked Sendable, conform to the most-derived protocol only, match access levels. If inheriting Mock isn't possible, keep both members and compose instead — let mock = Mock(), super.price → mock.price, adaptThrowing(...) → Mock.adaptThrowing(...) (spies-and-composition.md).
Full recipe (inheritance flattening, properties, subscripts, variadics, generics, statics, initializers): manual-mocking.md. Zero-parameter methods and property getters need the pinned-spy pattern described there — the macro-generated form silently mis-stubs them.
Quick verification checklist
A correct mock (manual or generated) round-trips:
let mock = FooMock()
when(mock.price(.any)).thenReturn(42)
let svc: Foo = mock // protocol-typed: avoids overload traps
_ = try svc.price("apple")
verify(mock.price(.equal("apple"))).called(1)
Known sharp edges
@Mockableonprotocol B: A→ compile errordoes not conform to protocol 'A'(inherited requirements never generated). Hand-write per manual-mocking.md.@Mockableonprotocol P: SomeClass→requires that 'MockP' inherit from 'SomeClass'. This is a class constraint, not protocol inheritance, and no hand-edit fixes the default output — the mock needs its superclass slot forSomeClass, and the default strategy needs it forMock. Add[.composition]. The option is opt-in: the macro can't tell a class from a protocol by name, so it never infers it.@Mockableonly accepts protocols — never a class. To fake a class, subclass it and back overrides with rawSpyproperties (spies-and-composition.md).finalclasses/members can't be faked either way; extract a protocol.- In a composed mock, plain
adapt(...)doesn't resolve — it's an instance method onMockand you didn't inherit it. Use the staticMock.adapt(...)/Mock.adaptThrowing(...).clear()does resolve:MockProvidingsupplies both the instance and static forms. - Hand-written composed mocks must spell
self.mock.name, not a baremock.name, wherever the spy is read inside a closure — settable members do exactly that, and Swift rejects the bare form withrequires explicit use of 'self' to make capture semantics explicit.supernever needed the qualifier.[.composition]output already does this. - Zero-arg members (
func start(), property getters):when(mock.getX()).thenReturn(v)does not reach the runtime member in macro-generated mocks. Manual mocks fix this with the pinned-spy pattern. - Stub API is
thenReturn/thenThrow/do— there is no.then. - Never write
thenReturn(()).Voidis in the default-value registry, so an unstubbed-> Voidmember just runs and records the call — stub it only to throw or to attach adoside effect. The registry also coversBool/String/Int/Double/Float/Array/Set/Dictionary/Optional, so those members are callable unstubbed too (usage.md — Default values). - Spy names come from the requirement, ignoring argument labels: methods use their name, subscripts are namespaced as
subscript+ParameterNames (subscript(row:column:)→subscriptRowColumn), settable members addset+Name. The prefix keeps a subscript from colliding with a method or variable of the same name. The compiler already rejects most same-key cases (two subscripts differing only by argument label, or avar xbeside afunc x(), are both invalid redeclarations); the one that compiles but mocks incorrectly is two methods differing only by argument label (fetch(id:)/fetch(name:)), which silently share a spy — rename one or vary the parameter types. Same-name/different-signature overloads are fine. - Settable members (
{ get set }) record reads and writes on separate spies:verify(mock.x)counts reads,verify(mock.x <- v)counts writes. A write never registers as a read. - Bare
mock.start()(zero-arg) is ambiguous on the mock type — call via a protocol-typed reference. - Literal arguments on the mock type dispatch to the interaction member (
mock.fetchUser(id: "1")returns anInteractioninstead of calling through) — call the mock via a protocol-typed reference.
References
manual-mocking.md— hand-writing mock classes; inheritance chains; pinned-spy patternspies-and-composition.md— usingSpydirectly; mocking classes; composinglet mock = Mock()for structs/actors/existing superclassesusage.md— when/verify/matchers/stubbing referencesendable.md— Swift 6 concurrency contract and non-Sendable workarounds
Signals
- GitHub stars
- 20
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
swift-mocking- Source
- github.com/danielcardonarojas/swift-mocking