Cuckoo mocks Swift objects with a compile-time generator, not reflection
Boilerplate-free mocking framework for Swift!
At a glance
- What is it?
- 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.
- Who is it for?
- 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.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 63 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- Brightify/Cuckoo1,744 ★ · +0
| Repository | 2026-10-16 | 2026-11-07 |
|---|---|---|
| Brightify/Cuckoo | 1.7k | 1.7k |
0 Stars in the last 22 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
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:
pod 'Cuckoo', '~> 2.0'Then a Run script build phase is added to the test target above the Compile Sources phase:
# 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.
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:
[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.
Editorial 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.
Frequently asked questions
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.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/brightify-cuckoo)Star history chart for your README
[](https://hysenlabs.com/projects/brightify-cuckoo)Was this useful?