Skip to content

Start

Install

Decoy is a Swift 6 package with no dependencies. Two edits to Package.swift:

Package.swift
let package = Package(
name: "MyApp",
// 1. Where to fetch it from.
dependencies: [
.package(url: "https://github.com/nerdmenot-swift/decoy", from: "1.0.0"),
],
targets: [
// 2. Which of your targets uses it, and which products they use.
.testTarget(name: "MyAppTests", dependencies: [
.product(name: "Decoy", package: "decoy"),
.product(name: "DecoyLocaleEN", package: "decoy"),
]),
]
)

Both are needed, and that is SwiftPM’s design rather than anything Decoy asks for. The top-level dependencies array only tells SwiftPM where to resolve the package from; it does not put anything on any target’s import path. Each target then names the products it actually uses, which is what lets a test target depend on Decoy while your shipping target does not — so fake-data generators and their corpora never end up linked into your app.

The second entry is a separate product on purpose. Decoy is the engine and DecoyLocaleEN is the data; you pick the locales you want and pay for those only.

Faker has no default one, and that is the point: the compiler asks you for it rather than letting Faker() build and then trap on the second line. There is a built-in corpus — LocaleCorpus.builtIn — but it defines eleven paths against the hundred and eighty-four the generators draw from, so almost everything would fail at run time. The generators that need no corpus at all, like checksums and UUIDs, take it explicitly:

var faker = Faker(seed: 1337, locale: .builtIn) // no corpus needed here
faker.crypto.ethereumAddress()

which reads as a decision rather than an accident.

import Decoy
import DecoyLocaleEN
// `faker` is a variable you create, not a global or a module. Name it what you like —
// every example on this site calls it `faker`, which is why the calls below read as
// `faker.something`.
var faker = Faker(seed: 1337, locale: DecoyLocaleEN.locale)
faker.person.fullName() // "Riley Bonneau"
faker.company.name() // "Crosslin inc."

It must be a var. Faker is a struct that carries its own random state, so each draw mutates it — that is what makes a run reproducible instead of depending on shared global state. Hold one per test, or let a forge hold it for you.

Three locales ship as importable modules — DecoyLocaleEN, DecoyLocaleDE and DecoyLocaleJA. The corpus holds sixty-four, and the other sixty-one are reached through the DecoyLocales product, which carries every blob as a resource and loads one by code:

import DecoyLocales
let fr = try DecoyLocales.locale("fr")

See Locales for which to use when.

A locale module compiles into your binary as ordinary Swift source — a base64 StaticString decoded once — so there is no resource loading at run time and nothing to ship beside your executable. That avoids Bundle.module, which is the most platform-fragile corner of SwiftPM.

DecoyLocales does use it, because sixty-four blobs cannot reasonably be sixty-four modules. That is the trade: reach for a module when your locale has one, and the resource product when it does not.

One module per locale means importing DecoyLocaleDE costs you de, en and base and nothing else. SwiftPM compiles only the targets you actually depend on.

Swift 6.0 or later, in Swift 6 language mode. macOS 13+, iOS 16+, or Linux.

Everything that decides a value — the RNG, the corpus reader, the calendar maths — imports nothing at all, so results are identical across platforms. Foundation appears only at the edge: the date namespace returns Foundation.Date behind #if canImport(Foundation), and Timestamp gives you the same instants without it.