Skip to content

Testing and Mocking

Husnain Ali edited this page Sep 27, 2026 · 2 revisions

Testing and Mocking

Test doubles ship in the library. Nothing touches the network in a unit test.

import Testing
@testable import SwiftNetworkKit

@Test func decodesTheProfile() async throws {
    let transport = MockNetworkTransport()
        .enqueueJSON(User(id: 1, name: "Ada"), status: 200)

    let client = NetworkClient(
        configuration: NetworkConfiguration(baseURL: "https://example.com"),
        transport: transport
    )

    let user = try await client.request(GetProfile())
    #expect(user.name == "Ada")
}

Simulating status codes and conditions

let transport = MockNetworkTransport()
    .enqueue(.status(500))                      // then...
    .enqueue(.status(429, headers: ["Retry-After": "2"]))
    .enqueue(.error(URLError(.notConnectedToInternet)))
    .enqueueJSON(order, status: 201)

// order-independent matchers instead of a FIFO queue:
let matched = MockNetworkTransport()
    .stub(method: .get, pathContains: "/me", with: .json(user))
    .stub(method: .post, pathContains: "/orders", with: .status(201))

Scenario presets

let client = NetworkClient(
    configuration: config,
    transport: MockScenario.tokenExpired(body: userJSON).transport()
)
// .happyPath / .tokenExpired / .offline / .rateLimited / .serverErrors

Other doubles

Double Use
URLProtocolStub Exercise the real URLSessionTransport wiring against canned responses
TestClock Drive retry / backoff / proactive-refresh timing with advance(by:), no real waiting
CapturingLogger Assert on emitted log lines (and that tokens are absent)
MockNetworkMonitor Push .satisfied / .unsatisfied into reachability streams
InMemoryOfflineStore Offline-queue tests without touching disk

Dependency injection

Inject the NetworkClient (or a protocol you define over it) into your repositories, and swap the transport in tests. The SwiftUI demo (Examples/SwiftUIDemo/) shows an AppContainer wiring pattern, plus a Diagnostics screen with two narrated mock-transport walkthroughs: 401 → refresh → retry, and an account-switch that demonstrates cache isolation and clearCache().

Clone this wiki locally