# Cuckoo mocks Swift objects with a compile-time generator, not reflection

> Cuckoo is a Swift mocking framework whose DSL was written to feel like Mockito, built around a command-line generator that emits mock classes before compilation. The cost of that design is a list of Swift constructs it will never be able to mock.

**Brightify/Cuckoo** — Boilerplate-free mocking framework for Swift!

- Repository: https://github.com/Brightify/Cuckoo
- Stars: 1,744 · Forks: 202
- Language: Swift
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/brightify-cuckoo

## Why a generator exists: Swift has no usable reflection

Cuckoo is split into two parts: a runtime and an OS X command-line tool called Cuckoonator. The reason for that split is stated without softening. Swift does not have proper reflection, so the project uses a compile-time generator that walks files you specify and generates supporting structs and classes consumed by the runtime in your test target.

That single architectural decision explains almost everything else about the framework, including its sharp edges. A runtime-only mocking library can inspect a type at runtime and decide what to override. A generator has to decide at generation time, from source it has parsed, and it emits concrete Swift code that the compiler then checks like any other code.

The generated files work based on inheritance and protocol adoption, which is where the boundary comes from. Only overridable things can be mocked. The README is candid that due to the complexity of Swift it is not easy to check for all edge cases, and it asks for an issue report if behaviour is unexpected.

Supported platforms are iOS 13+, macOS 10.15+, tvOS 13+ and watchOS 8+. The CocoaPods and SwiftPM badges in the README confirm both distribution paths are current.

## The list of things Cuckoo will never mock

There is a section titled what will not be supported, and it is worth reading before anything else in the README.

`struct` is not supported, with the workaround being to use a common protocol. Anything carrying the `final` or `private` modifier is not supported. Global constants and functions are not supported. Static properties and methods are not supported.

Each of those is common enough in idiomatic Swift that the list is a real constraint rather than a technical footnote. Structs and access control can therefore constrain which types a codebase makes mockable.

The stated workaround for structs is the practical guidance: define a protocol and mock the protocol instead of the type. That works, but it changes your production code to make your test code mockable, which is a design decision, not a testing convenience.

The features Cuckoo does claim are inheritance including grandparent methods, generics, simple type inference for instance variables working with initializers and `as TYPE` notation and overridable by specifying the type explicitly, and Objective-C mocks through OCMock.

## Installing via CocoaPods and the Xcode 15 sandbox trap

Swift Package Manager and CocoaPods both work. CocoaPods is the better documented path in the README, and it comes with a build phase that has to be placed exactly right.

The pod goes in your test target only:

```ruby
pod 'Cuckoo', '~> 2.0'
```

Then a Run script build phase is added to the test target above the Compile Sources phase:

```bash
# Skip for indexing
if [ $ACTION == "indexbuild" ]; then
  exit 0
fi

# Skip for preview builds
if [ "${ENABLE_PREVIEWS}" = "YES" ]; then
  exit 0
fi

"${PODS_ROOT}/Cuckoo/run"
```

The README is emphatic twice about ordering: the run script must be above the Compile Sources phase, and all paths inside the script must be absolute, with `PROJECT_DIR` used to point at the project directory. Input files can be declared in the phase's Input Files form. After the first run you locate `GeneratedMocks.swift` and drag it into your Xcode test target group.

One trap is worth isolating. From Xcode 15 the `ENABLE_USER_SCRIPT_SANDBOXING` build setting defaults to Yes, which sandboxed the script so it could not read input files or write output, and the phase fails. The fix is to set that setting to No.

Under Swift Package Manager you add the package URL `https://github.com/Brightify/Cuckoo.git`, add the plugin `CuckooPluginSingleFile` under Run Build Tool Plug-ins, and keep it confined to test targets.

## Cuckoofile.toml decides what gets generated and where it lands

Configuration lives in a `Cuckoofile.toml` at the root of your project, and it is where most first-run friction sits.

```toml
output = "Tests/Swift/Generated/GeneratedMocks.swift"

[modules.MyProject]
output = "Tests/Swift/Generated/GeneratedMocks+MyProject.swift"
imports = ["Foundation"]
publicImports = ["ExampleModule"]
testableImports = ["RxSwift"]
sources = [
    "Tests/Swift/Source/*.swift",
]
exclude = ["ExcludedTestClass"]
```

Each key does something specific. `output` is a fallback for modules with no override. `sources` is a glob list. `exclude` removes a class by name, and a commented `regex` key shows a regular expression filter is available for classes and protocols. `imports` are the standard imports added to the generated file, `publicImports` exist because imports are internal by default from Swift 6, and `testableImports` covers @testable imports.

Options under `[modules.MyProject.options]` cover `glob`, `keepDocumentation`, `enableInheritance`, `protocolsOnly` and `omitHeaders`. Docstrings are preserved by default and comments are omitted, which keeps generated files small.

A `[modules.MyProject.xcodeproj]` block lets the generator pull sources from an Xcode target instead of a filesystem glob, with a `path` and a `target`. You can define as many modules as you need, each with its own sources, options and output.

## CuckooPluginModular exists because SwiftPM test targets cannot see each other

`CuckooPluginSingleFile` writes every mock into one `GeneratedMocks.swift` inside derived data. In a Swift Package with multiple targets that is a problem, and the README explains why in concrete terms: each test target compiles independently and may not have visibility into types from unrelated modules.

`CuckooPluginModular` is the answer. It inspects your test target's dependencies and runs the generator once per dependency module, producing `GeneratedMocks_<ModuleName>.swift` for each. Each file lands in the dependency's own context rather than in one shared blob.

The cost is configuration. A `Cuckoofile.toml` entry is required for every module you want mocks for, and the plugin looks up the test target's own name, so an entry keyed on the test target rather than the source module:

```toml
[modules.TargetATests]
imports = ["Foundation"]
testableImports = ["TargetA"]
sources = [
    "Sources/TargetA/InternalProtocolA.swift"
]
```

The `testableImports` comment in the README notes the case this exists for: ProtocolA is internal to TargetA, so an ordinary import will not reach it. Modules with no matching entry produce an empty file and no mocks, which fails quietly rather than loudly.

If a protocol lives on a type you do not own, or on a type you cannot make `open`, neither plugin helps. The limitation is in the type, not in the plugin.

## Against Swift Testing's built-in mocking, and what the repository itself says

The difference in shape is the API surface. Cuckoo's DSL was deliberately built to be similar to Mockito, which the README states as its founding reason: the project was created due to a lack of a proper Swift mocking framework, and the DSL was made familiar so anyone coming from Java or Android could pick it up. That familiarity is the argument. Switching from Mockito to Cuckoo should feel like nothing. Switching from one Apple tool to another teaches a different vocabulary.

The difference in maturity is the argument against. Cuckoo's release history is active: 2.2.1 on 2026-03-02, 2.3.0 on 2026-06-03 and 2.4.0 on 2026-07-29, with the last push on 2026-09-05. The repository is not archived and carries a MIT licence, which permits use inside proprietary test targets and modification of the generator.

The repository layout shows how the project builds itself: a Tuist workspace with `Workspace.swift`, `Project.swift` and a `Tuist/` directory, a Makefile whose `dev` target kills Xcode, creates an empty `Tests/Swift/Generated/GeneratedMocks.swift`, runs `mise install`, then `TUIST_PROJECT_DIR=$(PWD) tuist generate --no-open` before opening `Cuckoo.xcworkspace`. There is also an `OCMock/` and `OCMockWrapper/` pair for the Objective-C support, a `Gemfile` for fastlane, and a `Cuckoofile.toml` at the root for its own tests. That toolchain is heavier than the CocoaPods story suggests, and it is a fair signal of what the maintainers use day to day.

## Conclusion

Cuckoo fits a Swift team with an existing Xcode test target that wants Mockito-style stubs for protocols and open classes, especially one already generating a workspace with Tuist or CocoaPods where a build phase fits naturally. It does not fit SwiftUI code that depends on structs, final types and static members, since the README lists all of those as unsupported. Verify first by adding Cuckoofile.toml with one module entry and one protocol, running the generator, and confirming the emitted type compiles inside your test target, because the README asks you to file an issue for unexpected behaviour rather than promising the edge cases are covered.

## FAQ

### How do I install Cuckoo in a Swift project?

Through CocoaPods add pod 'Cuckoo', '~> 2.0' to your test target and add a Run script build phase above Compile Sources that calls "${PODS_ROOT}/Cuckoo/run", or through Swift Package Manager by adding https://github.com/Brightify/Cuckoo.git and the CuckooPluginSingleFile build tool plug-in. Add it to test targets only.

### Why does my Cuckoo run script fail in Xcode 15 or later?

From Xcode 15 the ENABLE_USER_SCRIPT_SANDBOXING build setting defaults to Yes, which blocks the script from reading input and writing output files. The README says to change that setting to No.

### What can Cuckoo not mock in Swift?

The README lists structs, anything with a final or private modifier, global constants and functions, and static properties and methods. For structs the stated workaround is to mock a common protocol instead.

### When should I use CuckooPluginModular instead of CuckooPluginSingleFile?

Use the modular plugin in a Swift Package with multiple targets, because each test target compiles independently and may not have visibility into types from unrelated modules. It generates a separate GeneratedMocks_<ModuleName>.swift per dependency and requires a Cuckoofile.toml entry per module.

### What platforms does Cuckoo support?

The README states iOS 13 or later, macOS 10.15 or later, tvOS 13 or later and watchOS 8 or later.

## Sources

- [Brightify/Cuckoo on GitHub](https://github.com/Brightify/Cuckoo)
- [Issues](https://github.com/Brightify/Cuckoo/issues)
- [License: MIT](https://github.com/Brightify/Cuckoo/blob/master/LICENSE)
- [README](https://github.com/Brightify/Cuckoo/blob/master/README.md)
- [Releases](https://github.com/Brightify/Cuckoo/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/brightify-cuckoo
