swift-sourcery-templates
swift-sourcery-templates copied to clipboard
Advanced Protocol Mock and Type Erasure Code-generation templates for Swift.
swift-sourcery-templates
Advanced Protocol Mock and Type Erasure Code-generation templates for Swift language (using Sourcery).
This repository contains two code-generation templates (to be used along with Sourcery engine):
-
Mocks.swifttemplate
— to generate advanced protocol mock classes that can be used as test doubles in place of object dependencies for unit-testing; -
TypeErase.swifttemplate
— to generate advanced type erasures.
Both templates support protocols with associated types and generic functions (with constraints on generic types), provide reasonable default values for primitive types, and support advanced types and use cases typically found in projects that use RxSwift, and, in particular, User's RIBs frameworks.
Rationale
Code-generation is an extremely powerful technique to improve the developer team's productivity by eliminating time-consuming manual tasks, and ensuring consistency between different parts of the codebase. Typical examples include generating API wrappers for database and network services, data structure representations as native types in a specific programming language, generating test doubles and mocks for unit-testing, etc.
Due to Swift language's static nature and a (very) limited runtime reflection capabilities, it's arguably the only modern language with no mocking framework, which makes it hard for development teams to apply patterns typical for more dynamic languages when writing unit-tests.
The common approach to improving developer's productivity has been to write tools that parse the source code of the program,
and automatically generate mock class definitions that can be later used in place of actual object's dependencies in unit tests,
to be able to instantiate the object in isolation from the rest of the system and test its behavior by analyzing side effects it
performs on dependencies. There are tools like Sourcery,
SwiftyMocky, Cuckoo,
there's even a plug-in for the JetBrain's AppCode.
These tools and plug-ins are capable of auto-generating mock class definitions based on reading source type declarations,
however, I was personally struggling to find a comprehensive solution so far, that would cover some advanced coding patterns,
in particular, typical for programs that combine RxSwift
and Uber's RIBs
frameworks.
Usage
Swift Package Manager (SPM) prebuild plugin
A prebuild SPM plugin runs before the project is built, allowing to generate code based on the project source files.
- To add the plugin to your project, add the plugin dependency to your
Package.swift
:
// Package.swift
// swift-tools-version: 5.9 // The minimum supported swift-tools version is 5.6
import PackageDescription
let package = Package(
name: "YourPackageName",
products: [
// ...
],
dependencies: [
// ...
.package(url: "https://github.com/ivanmisuno/swift-sourcery-templates.git", from: "0.2.2"),
],
targets: [
.target(
name: "YourTarget",
dependencies: [
// ...
],
plugins: [
.plugin(name: "SourcerySwiftCodegenPlugin", package: "swift-sourcery-templates")
]
),
.testTarget(
name: "YourTargetTests",
dependencies: [
// ...
.target(name: "ExampleProjectSpm"),
],
plugins: [
.plugin(name: "SourcerySwiftCodegenPlugin", package: "swift-sourcery-templates")
]
),
]
)
The SourcerySwiftCodegenPlugin
depends on the binary distribution of the Sourcery CLI.
The first time it is invoked, Xcode/build system would ask for a permission to run the plugin.
You might also get an unverified developer warning when the plugin tries to invoke Sourcery for the first time. To fix it, please use the woraround for removing Sourcery from quarantine:
xattr -dr com.apple.quarantine <...Derived Data Folder>/SourcePackages/checkouts/swift-sourcery-templates/Plugins/Sourcery/sourcery.artifactbundle/sourcery/bin/sourcery
- Configuring code generation
The codegeneration is configured per target in an SPM project. Put a *.sourcery.yml
config file in the target's source folder
(refer to the example project):

Here, ExampleProjectSpm
and ExampleProjectSpmTests
are product targets, for which we want to enable code generation.
We want to generate type erasures and interface mocks. Type erasure classes should be available for use in the main target,
while mocks should be available in the test target.
Here is caveat: Some mock classes should be generated for interfaces that are declared in external packages (e.g. for the RIBs
package).
We need to (a) include the external package sources in the config file:
# .Sourcery.Mocks.yml
sources:
- ${SOURCERY_TARGET_ExampleProjectSpm}
- ${SOURCERY_TARGET_ExampleProjectSpmTests}/SourceryAnnotations
- ${SOURCERY_TARGET_ExampleProjectSpm_DEP_RIBs_MODULE_RIBs}
We're doing a little trickery here to work around an issue with Sourcery — when invoked from a build tool SPM plugin, Sourcery cannot analyze the package structure, so the plugin exports the package structure via environment variables. In the excample above:
-
SOURCERY_TARGET_ExampleProjectSpm
refers to theExampleProjectSpm
target source location; -
SOURCERY_TARGET_ExampleProjectSpmTests
refers to theExampleProjectSpmTests
target source location; -
SOURCERY_TARGET_ExampleProjectSpm_DEP_RIBs_MODULE_RIBs
refers to theRIBs
module source location (RIBs
is the dependency of theExampleProjectSpm
target).
The plugin exports all package dependencies' source locations via environment variable similarly:
-
SOURCERY_TARGET_<target_name>_DEP_<dependecy_module>_MODULE_RIBs
-
SOURCERY_TARGET_<target_name>_DEP_<dependecy_target>_TARGET_RIBs
[!NOTE] For the complete Sourcery config file reference, please refer to the official documentation.
- Finding the generated files
The above might seem tricky at first. The plugin helps debug setup issues by emitting invocation and debug logs to the build log:

The invocation log also contains all exported environment variables for the dependencies.
Podfile
Following examples refer to the tutorial project (WIP), please feel free to clone and see its workings. Accompanying blog post is coming.
-
Add
SwiftMockTemplates
Pod to your projects's Test target:pod 'SwiftMockTemplates', :git => 'https://github.com/ivanmisuno/swift-sourcery-templates.git', :tag => '0.1.0'
(I'll release it as a public Podspec once I complete unit-tests).
-
Add
.sourcery-mocks.yml
config file to the project's root:sources: - Tutorial_RIBs_CodeGeneration - Tutorial_RIBs_CodeGenerationTests/Mocks/AnnotatedRIBsProtocols.swift - Pods/RIBs templates: - Pods/SwiftMockTemplates/templates/Mocks.swifttemplate output: Tutorial_RIBs_CodeGenerationTests/Mocks args: testable: - Tutorial_RIBs_CodeGeneration import: - RIBs - RxSwift - RxTest excludedSwiftLintRules: - force_cast - function_body_length - line_length - vertical_whitespace
-
Add
codegen.sh
script that will runSourcery
with the above config file:#!/bin/bash # the directory of the script. all locations are relative to the $DIR DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PARENT_DIR="$DIR/.." SOURCERY_DIR="$PARENT_DIR/Pods/Sourcery" SOURCERY="$SOURCERY_DIR/bin/sourcery" "$SOURCERY" --config "$PARENT_DIR"/.sourcery-mocks.yml $1 $2
Note that
sourcery
executable is installed inPods
folder. -
Annotate protocols in your code for which you'd like to generate mock classes with the following annotation:
/// sourcery: CreateMock
There are more advanced use cases, the documentation is coming.
-
Run following command to generate mock classes:
scripts $ ./codegen.sh [--disableCache]
This command will generate
Mocks.generated.swift
file in theoutput
folder as specified in the config file. -
Add the generated file to the test target in the Xcode project.
License
Copyright 2018 Ivan Misuno
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.