Skip to content
Development
Skill

/integration-test-scaffold

Generate cross-module test harness with mock servers, in-memory stores, and test configuration. Use when testing networking + persistence + business logic together.

From plugin
rshankras-apple-skills
848181 skills

Claude Code

$ npx -y skills add rshankras/claude-code-apple-skills --skill integration-test-scaffold --agent claude-code

SKILL.md

integration-test-scaffold.SKILL.md
name: integration-test-scaffold
description: Generate cross-module test harness with mock servers, in-memory stores, and test configuration. Use when testing networking + persistence + business logic together.
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
last_verified: 2026-07-16
review_by: 2027-06-22

Integration Test Scaffold

Generate test infrastructure for testing multiple modules working together — networking + persistence + business logic — without hitting real servers or databases.

When This Skill Activates

Use this skill when the user:

  • Wants to "test the full stack" or "integration test"
  • Needs a mock server or mock API
  • Wants to test networking + caching together
  • Asks about "end-to-end tests without real servers"
  • Needs to test data flow across layers (API → Repository → ViewModel)
  • Mentions "test harness" or "test environment"

Why Integration Tests

Unit tests:         Test ONE thing in isolation (fast, focused)
Integration tests:  Test MULTIPLE things together (realistic, catches wiring bugs)

Unit test passes:   PriceCalculator works alone ✅
Integration test:   PriceCalculator + API + Cache work together ✅
                    (Catches: wrong data format, missing mapping, race conditions)

Process

Phase 1: Map the Integration Boundaries

Identify what modules interact:

Grep: "import |@testable import" to find module dependencies
Read: source files to understand data flow

Common integration boundaries:

  • **Network → Parser → Repository** (API data flow)
  • **Repository → ViewModel → View** (UI data flow)
  • **UserAction → Service → Storage → Notification** (write flow)

Phase 2: Configuration Questions

Ask via AskUserQuestion:

1. **What layers to integrate?**

  • Network + Repository
  • Repository + ViewModel
  • Full stack (Network → ViewModel)
  • Custom combination

2. **Mock strategy?**

  • URLProtocol-based mock server (intercepts real URLSession)
  • Protocol-based mock (swap implementation)
  • In-memory database (SwiftData/CoreData)

Phase 3: Generate Mock Server

URLProtocol Mock Server

// Tests/Infrastructure/MockURLProtocol.swift

final class MockURLProtocol: URLProtocol {
    /// Map of URL path → (status code, response data)
    static var mockResponses: [String: (Int, Data)] = [:]
    /// Captured requests for verification
    static var capturedRequests: [URLRequest] = []
    /// Simulated delay
    static var responseDelay: TimeInterval = 0

    static func reset() {
        mockResponses = [:]
        capturedRequests = []
        responseDelay = 0
    }

    override class func canInit(with request: URLRequest) -> Bool {
        true  // Intercept all requests
    }

    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        request
    }

    override func startLoading() {
        Self.capturedRequests.append(request)

        let path = request.url?.path ?? ""
        let (statusCode, data) = Self.mockResponses[path] ?? (404, Data())

        let response = HTTPURLResponse(
            url: request.url!,
            statusCode: statusCode,
            httpVersion: nil,
            headerFields: ["Content-Type": "application/json"]
        )!

        if Self.responseDelay > 0 {
            Thread.sleep(forTimeInterval: Self.responseDelay)
        }

        client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
        client?.urlProtocol(self, didLoad: data)
        client?.urlProtocolDidFinishLoading(self)
    }

    override func stopLoading() {}
}

Mock Server Helper

// Tests/Infrastructure/MockServer.swift

struct MockServer {
    /// Register a successful JSON response for a path
    static func respondWith<T: Encodable>(
        _ value: T,
        for path: String,
        statusCode: Int = 200
    ) {
        let data = try! JSONEncoder().encode(value)
        MockURLProtocol.mockResponses[path] = (statusCode, data)
    }

    /// Register a raw data response
    static func respondWith(
        data: Data,
        for path: String,
        statusCode: Int = 200
    ) {
        MockURLProtocol.mockResponses[path] = (statusCode, data)
    }

    /// Register an error response
    static func respondWithError(
        for path: String,
        statusCode: Int = 500
    ) {
        let error = ["error": "Server Error"]
        let data = try! JSONEncoder().encode(error)
        MockURLProtocol.mockResponses[path] = (statusCode, data)
    }

    /// Create a URLSession configured to use mock responses
    static func session() -> URLSession {
        let config = URLSessionConfiguration.ephemeral
        config.protocolClasses = [MockURLProtocol.self]
        return URLSession(configuration: config)
    }
}

Phase 4: Generate In-Memory Store

SwiftData In-Memory

// Tests/Infrastructure/InMemoryModelContainer.swift

import SwiftData

enum TestModelContainer {
    @MainActor
    static func create(for types: any PersistentModel.Type...) -> ModelContainer {
        let schema = Schema(types)
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        return try! ModelContainer(for: schema, configurations: config)
    }
}

// Usage in tests:
@Test("saves and fetches items")
@MainActor
func savesAndFetches() async throws {
    let container = TestModelContainer.create(for: Item.self)
    let context = container.mainContext

    let item = Item(title: "Test")
    context.insert(item)
    try context.save()

    let fetched = try context.fetch(FetchDescriptor<Item>())
    #expect(fetched.count == 1)
}

UserDefaults In-Memory

// Tests/Infrastructure/MockUserDefaults.swift

final class MockUserDefaults: UserDefaults {
    private var storage: [String: Any] = [:]

    override func object(forKey defaultName: String) -> Any? {
        storage[defaultName]
    }

    override func set(_ value: Any?, forKey defaultName: String
Read more
@rshankras
Ships withrshankras-apple-skills

A collection of Claude Code skills for iOS, macOS, watchOS, visionOS, and Apple platform development. These skills help you plan and build apps, maintain code quality, ensure HIG compliance, and guide you from idea to App Store.

Stars on GitHub

Momentum

stars in total
848
Star history of rshankras/claude-code-apple-skills on GitHub, a chart by star-history.com

Other skills on rshankras-apple-skills.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about integration-test-scaffold.

Which coding agents can use integration-test-scaffold?
integration-test-scaffold installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.