Generate mocks in seconds, not minutes.
MockableKit is a Swift package that lets any Decodable struct generate realistic mock data using Gemini AI. It reads your struct's field names and types automatically via the Decodable protocol, no manual setup per struct, no hand-written JSON fixtures, no maintenance overhead.
Writing mock data by hand is slow and brittle, fields change, fixtures go stale, and boilerplate piles up. MockableKit eliminates that by generating context-aware, realistic test data directly from your Swift types using a large language model. Drop it into any XCTest target or SwiftUI Preview and get meaningful data with a single line of code.
| Platform | Minimum Version |
|---|---|
| iOS | 13.0+ |
| macOS | 13.0+ |
| watchOS | 9.0+ |
| tvOS | 16.0+ |
Requires Swift 5.9+ and Xcode 15+.
MockableKit uses a probe decoder, a fake Decoder that intercepts all decode(...) calls when Swift tries to decode your struct. This captures every field name and its Swift type without needing a real JSON payload or a live instance. That schema is sent to Gemini, which returns fitting JSON, and the result is decoded back into your type.
Responses are cached to disk by default so repeated calls with the same type, schema, count, locale, and model never hit the API again, making tests and previews fast after the first run.
MockableKit is distributed exclusively via Swift Package Manager (SPM). CocoaPods and Carthage are not supported.
- In Xcode: File → Add Package Dependencies
- Enter
https://github.com/senolmurat/MockableKit - Add
MockableKitto your test target (not your app target)
Or add to Package.swift:
dependencies: [
.package(url: "https://github.com/senolmurat/MockableKit", from: "1.0.0")
],
targets: [
.testTarget(
name: "YourAppTests",
dependencies: ["MockableKit"]
)
]Set your Gemini API key once, best done in your test setUp() or AppDelegate:
import MockableKit
MockableConfiguration.shared.apiKey = "YOUR_GEMINI_API_KEY"Get a free key at aistudio.google.com/app/apikey.
MockableConfiguration.shared.model = "gemini-2.5-flash-lite" // default
MockableConfiguration.shared.locale = "English" // affects names, addresses
MockableConfiguration.shared.debugLogging = true // prints generated JSON
MockableConfiguration.shared.cacheEnabled = true // disk cache on by default
MockableConfiguration.shared.maxTokens = 1024 // max LLM response tokensimport MockableKit
struct User: Mockable {
let id: Int
let name: String
let email: String
let age: Int
let isActive: Bool
}That's it. No extra code needed.
// Single instance
let user = try await User.mock()
print(user.name) // "John Doe"
print(user.email) // "john.doe@example.com"
print(user.age) // 28
// Multiple instances
let users = try await User.mocks(count: 5)Use the completion-based overloads when you're in a synchronous context or targeting iOS 13 where structured concurrency may not be available in all call sites:
// Single instance, completion receives Self? (nil on failure)
User.mock { user in
guard let user else { return }
self.currentUser = user
}
// Multiple instances, completion receives [Self]? (nil on failure)
User.mocks(count: 5) { users in
guard let users else { return }
self.userList = users
}
// Per-call cache control
User.mock(cacheEnabled: false) { user in
// Always fetches fresh data, ignoring the global cacheEnabled setting
self.currentUser = user
}Note: The completion is called on an arbitrary background thread. Dispatch to the main queue if you're updating UI.
struct UserCard_Previews: PreviewProvider {
static var previews: some View {
AsyncPreviewWrapper {
let user = try await User.mock()
return UserCard(user: user)
}
}
}final class UserTests: XCTestCase {
func testUserDisplayName() async throws {
let user = try await User.mock()
XCTAssertFalse(user.name.isEmpty)
}
}MockableKit includes a disk-backed cache that stores Gemini responses in the system caches directory under MockableKit/. Cache keys are a stable FNV-1a hash of the type name, schema, count, locale, and model, so the key changes automatically whenever any of those inputs change.
- Cache is enabled by default (
cacheEnabled = trueonMockableConfiguration.shared). - A cache hit skips the network call entirely and returns the stored JSON immediately.
- Cache entries never expire and persist across app/test process launches.
- When
debugLoggingis on, cache hits are logged:[MockableKit] Cache hit for key: mock_<hash>.
MockableConfiguration.shared.cacheEnabled = falselet freshUser = try await User.mock(cacheEnabled: false)
User.mock(cacheEnabled: false) { user in
self.currentUser = user
}// Deletes all cached responses from disk
MockableConfiguration.clearCache()Override mockContext to give the LLM more hints about what your type represents:
struct Article: Mockable {
static var mockContext: String { "Tech news article" }
let headline: String
let author: String
let publishedAt: String // LLM will generate a date string
let readTimeMinutes: Int
}MockableKit automatically handles:
| Swift Type | Generated Example |
|---|---|
String |
Context-aware text |
Int / Double / Float |
Realistic numbers |
Bool |
true or false |
[String] |
Array of strings |
Nested Decodable |
Nested JSON object |
Optional<T> |
May be null or a value |
do {
let user = try await User.mock()
} catch MockableError.missingAPIKey {
print("Set MockableConfiguration.shared.apiKey first")
} catch MockableError.apiError(let code, let body) {
print("API error \(code): \(body)")
} catch MockableError.decodingFailed(let json, let error) {
print("Bad JSON: \(json)")
print("Decode error: \(error)")
} catch {
print(error.localizedDescription)
}MockableKit/
├── Package.swift
├── Sources/
│ └── MockableKit/
│ ├── MockableKit.swift # Public protocol + extensions (async & completion)
│ ├── MockableConfiguration.swift # API key, model, locale, cache settings
│ ├── MockEngine.swift # LLM call, prompt building, cache integration
│ ├── MockCache.swift # Disk-backed FNV-1a keyed cache
│ ├── SchemaExtractor.swift # Probe decoder + field extraction
│ └── MockableError.swift # Error types
└── Tests/
└── MockableKitTests/
└── MockableKitTests.swift
- Test targets only, Don't ship MockableKit in your production app target. Add it only to test and preview targets.
- API key security, Never hardcode your key. Use
ProcessInfo.processInfo.environment["GEMINI_API_KEY"]in CI. - Async, All
async throwsoverloads require iOS 13+ with Swift Concurrency back-deployment or a wrappingTask {}. - Completion handlers, The
(Self?) -> Voidoverloads are available for all supported platforms and require no concurrency runtime support at the call site. - Thread safety,
MockCacheis anactor; all reads and writes are safe from any thread or task.
MockableKit is available under the MIT license.
Keywords: Swift mock data generator, Decodable mock Swift, XCTest mock data, SwiftUI preview mock data, AI test data Swift, Gemini Swift package, Swift Package Manager testing tools