ios-testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ios-testing (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Approach: F.I.R.S.T-First, Production-Ready Tests -- Every test produced by this skill must be Fast, Isolated, Repeatable, Self-validating, and Thorough. Architecture changes to production code and improvements to the test suite both follow phased, low-risk PRs tracked in a refactoring/ directory.Enterprise-grade testing skill covering 11 areas across all iOS architectures. Opinionated: prescribes Swift Testing (@Test/@Suite/#expect) for all new tests on Xcode 16+, protocol-based mocks with call tracking, F.I.R.S.T compliance checks on every generated test, and architecture-specific patterns for MVVM (UIKit+Combine, SwiftUI+@Observable), VIPER, and TCA.
Target: 90% unit tests (mocked, milliseconds) | 8% integration (real DB/network stub) | 2% UI (XCUITest). When in doubt, write a unit test.
| Principle | What it means | Violation signal |
|---|---|---|
| Fast | Unit tests should run in milliseconds. Avoid unnecessary waits. | Real URLSession, Thread.sleep, disk I/O |
| Isolated | Tests don't share state. Order doesn't matter. | static var, setUp skipped, test A breaks B |
| Repeatable | Same result on any machine, any time. | Date(), UUID(), random data, time zones |
| Self-validating | Pass or fail -- no log inspection. | print(result) with no assertion |
| Thorough | Happy path + error path + edge cases. | Only green-path tests, 0% error coverage |
Need UI testing? -> XCTest (XCUITest) only
Need performance testing? -> XCTest (measure {}) only
Need attachments? -> XCTest only
Need ObjC support? -> XCTest only
New unit/integration test? -> Swift Testing (preferred)
Existing XCTest? -> Migrate incrementally, both coexistSoft check (report + continue)? -> #expect(expr)
Hard precondition (halt on fail)? -> try #require(expr)
Unwrap optional + use value? -> let val = try #require(optional)
Check error thrown? -> #expect(throws: Type.self) { }
Check nothing thrown? -> #expect(throws: Never.self) { }
Record unconditional failure? -> Issue.record("msg")@Published + Combine (XCTest)?
-> dropFirst() + expectation + sink + waitForExpectations
@Observable (XCTest)?
-> withObservationTracking + expectation + waitForExpectations
@Observable sync?
-> direct assertion, no waiting needed
Swift Testing + async?
-> confirmation() for event counting
-> withCheckedContinuation for completion handlers
-> for await in stream.prefix(N) for AsyncStream
TCA?
-> TestStore + await send/receive + TestClock
ViewModel with internal Task {}?
-> expectation/confirmation or withMainSerialExecutorNeed to verify method WAS CALLED? -> Spy/Mock (tracks calls)
Need to provide CANNED RESPONSE? -> Stub (returns fixed data)
Need BOTH? -> Mock = Stub + Spy
System singleton? -> Protocol wrapper + inject
1-2 dependency methods? -> Closure-based injection
3+ methods or call counting? -> Protocol-based injection
VIPER protocols (many modules)? -> Sourcery AutoMockableMVVM + SwiftUI + @Observable?
-> references/observable-testing.md + references/async-testing.md
MVVM + UIKit + Combine?
-> references/xctest-patterns.md (Combine section)
VIPER / Clean Architecture?
-> references/viper-testing.md
TCA (Composable Architecture)?
-> references/tca-testing.md
UI Testing?
-> references/ui-testing.md
Snapshot Testing?
-> references/snapshot-testing.mdWhen: Adding tests for a new or untested component.
stubbed* returns and *CallCount tracking@MainActor struct ViewModelTests (Swift Testing) or @MainActor final class ... : XCTestCasereferences/enterprise-testing.md)When: Modernizing a legacy test suite on Xcode 16+. Never in same PR as production changes.
references/refactoring-workflow.md -- create migration plan in refactoring/references/refactoring-workflow.mdreferences/anti-patterns.md C5/C6/C7)setUp/tearDown with stored properties or init@Suite("ComponentName") and descriptive @Test("does X when Y") names@MainActor on @Suite type when testing isolated ViewModels@Suite(.serialized) + log taskrefactoring/ planWhen: First encounter with a legacy test suite, or preparing a quality report.
references/anti-patterns.md detection checklist and grep script#expect in XCTestCase), also check M6: is the #expect argument a pre-evaluated Bool variable (e.g. let isValid = ...; #expect(isValid))? These are separate issues — M6 persists even after the context is fixed. Always report both.wait(for:) deadlocks, real network calls, shared staticsclass.*ViewModel without corresponding *Tests filerefactoring/test-debt.md with severity-ranked findingsWhen: Testing VIPER modules, TCA features, or specific architecture patterns.
references/viper-testing.md)@MainActor, override dependencies, assert exhaustively (references/tca-testing.md)withObservationTracking or direct sync assertions (references/observable-testing.md)dropFirst() + sink + scheduler injection (references/xctest-patterns.md)<critical_rules> When generating or reviewing tests, every output must be F.I.R.S.T-compliant, production-ready, and PR-shippable. ALWAYS:
@Test/@Suite/#expect) for new tests -- XCTest only when Xcode 15 or older, or UI/performance testingand in test names@MainActor when SUT is @MainActor-isolatedtry #require() before unwrapping optionals in test setupItem.sample()) not raw initializers.timeLimit(.minutes(1)) on any test that touches async code#expect() -- never pre-evaluate to Bool$0 in send/receive closures, never use XCTAssertEqual; use case key paths for receive<thought> identifying: what public behaviors to test, what dependencies to mock, which architecture pattern applies, and which F.I.R.S.T principle is most at riskwait(for:) cannot pump the main queue inside an async function" is a 🔴 Critical issue, not a "hmm, worth looking at". Without an explicit label, reviewers can't triage and the finding gets deferred.</critical_rules>
Use this table to classify any test issue you surface — during generation, diagnosis, or review.
| Severity | Meaning | Examples |
|---|---|---|
| 🔴 Critical | Test hangs, crashes, or lies — produces wrong signal | wait(for:) inside async (hang), XCTAssert* inside @Test (silently swallowed), #expect inside XCTestCase (no-op), real URLSession in unit test (flaky + slow), shared mutable static var state between tests |
| 🟡 High | Test runs but breaks F.I.R.S.T or gives false confidence | Missing @MainActor on @MainActor-isolated SUT, no await fulfillment(of:) for async (timeout race), missing dropFirst() on Combine, no memory leak detection on ViewModel, testing only the happy path |
| 🟢 Medium | Functional but fragile or hard to maintain | Raw initializers instead of test data factories, and in test names, over-mocked boundary, mock over 100 lines, setUp/tearDown doing unrelated work |
How to apply: When answering "why does my test X?" or "what's wrong with this test code?", the response must lead with 🔴 Critical — <short label> (or High/Medium) before the explanation. If the cause is a hanging async test, it's Critical. If it's a missing @MainActor, it's High. Never omit the label.
<fallback_strategies> When migrating or writing tests, you may hit stubborn issues. If the same problem appears twice, break the loop:
@MainActor to the entire test type declaration -- not just the method. Why: Swift Testing infers isolation from the type, not individual methods. If error persists, wrap assertions in await MainActor.run { } as last resort.@Suite(.serialized). Log a task in refactoring/ to investigate. Why: Swift Testing runs all tests in ONE PROCESS with cooperative concurrency (unlike XCTest which used separate processes), so shared mutable state causes non-deterministic failures.@Test functions -- tests pass even when assertions fail.@Test context.withCheckedContinuation to bridge. Why: confirmation() checks the count when its closure returns, not when a callback fires later.</fallback_strategies>
Before finalizing generated or reviewed tests, verify ALL:
[] F.I.R.S.T compliant -- no real network, no shared state, no Date()/UUID(), has assertion, covers error paths
[] Correct framework -- Swift Testing for new code (Xcode 16+), no framework mixing within a function
[] One concept per test -- no "and" in test name, no multiple unrelated assertions
[] Mock protocol-based -- no concrete type dependencies, no URLSession in unit tests
[] @MainActor -- present on test type when SUT is @MainActor
[] Memory leak detection -- addTeardownBlock or makeSUT pattern present
[] Async safety -- await fulfillment(of:) not wait(for:), timeLimit set
[] Test data factory -- Item.sample() pattern, not raw initializers
[] Error path coverage -- at least one test for the failure case
[] Naming -- "test_<method>_<condition>_<expected>" or @Test("does X when Y")
[] No framework mixing -- no XCTAssert in @Test, no #expect in XCTestCase
[] Architecture-specific -- correct patterns for MVVM/VIPER/TCA| Test context | Companion skill | When |
|---|---|---|
Testing @Observable / @MainActor ViewModels | swiftui-mvvm skill | ViewModel structure, ViewState enum |
| Testing UIKit + Combine ViewModels | uikit-mvvm skill | Combine publisher testing, Coordinator testing |
| Testing async/await and actor-isolated code | swift-concurrency skill | withMainSerialExecutor, Clock injection |
| Testing code with GCD/OperationQueue | gcd-operations skill | Dispatch queue mocking |
| Reference | When to Read |
|---|---|
references/rules.md | Do's and Don'ts quick reference: priority rules and critical anti-patterns |
references/swift-testing-framework.md | @Test, @Suite, #expect, #require, parameterized tests, tags, confirmation(), known issues, coexistence dangers, parallel execution, traits |
references/xctest-patterns.md | XCTestCase structure, mock pattern, memory leak detection, Combine @Published testing, scheduler injection, coordinator testing |
references/async-testing.md | withMainSerialExecutor, await fulfillment, Clock injection, AsyncStream testing, confirmation() semantics, timeout patterns |
references/observable-testing.md | withObservationTracking, willSet semantics, sync vs async @Observable testing, NavigationPath, sheet/cover state |
references/viper-testing.md | Presenter/Interactor/Router testing, weak reference enforcement, module assembly, entity boundaries, mock management |
references/tca-testing.md | TestStore, exhaustive assertions, receive() case key paths, TestClock, dependencies, navigation, @Shared state |
references/ui-testing.md | Page Object Model, waitForExistence, system alerts, launch arguments, deep link testing, screenshots, accessibility audit |
references/snapshot-testing.md | Device pinning, recording modes, CI configuration, SwiftUI hosting, precision settings, multi-strategy |
references/integration-testing.md | URLProtocol mocking, Core Data /dev/null, SwiftData, Keychain protocol wrapper, UserDefaults isolation, system services |
references/enterprise-testing.md | OAuth token refresh, feature flags, analytics spies, deep linking, push notifications, memory leak detection, test data builders, accessibility |
references/anti-patterns.md | Detection checklist with grep patterns, severity-ranked (Critical/High/Medium), framework mixing dangers |
references/test-organization.md | File structure, naming, Test Plans, CI configuration, parallel testing, coverage targets, flaky test quarantine, time budgets |
references/refactoring-workflow.md | Complete assertion mapping table, lifecycle mapping, migration plan, PR sizing, coexistence rules, common mistakes |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.