How to write a Swift Mock Code Generator in Swift

Incontent with the Swift mocking solutions available I thought about how to write a mock code generator for Swift by myself. Getting started is surprisingly easy with Swift packages like Swift Argument Parser and SwiftSyntax.

In short time I hit the limits of SwiftyMocky. First, it dropped the async keyword of protocol function implementations in generated mock code. That does not work with @objc protocols where those have to be async. Second, it does not support actor protocols (yet).

Beyond that, SwiftyMocky builds on top of Sourcery which offers only a mediocre experience, too. I do not intend to sound naggy. I know mocking in Swift isn’t that much fun with the language constraints (or advantages, depending on the context). I just had some specific requirements in my head. In summary: the mock code generator should be portable in the meaning of being a self-contained Swift package with only such dependencies on its own. This way it can be used as a build time dependency or as an Xcode package plugin and otherwise it does not come with any additional requirements to its build environment than the standard Xcode toolchain which is especially nice for CI runners.

My Example Project

I have set up an example project and published it on GitHub. The individual commits correspond to the following steps.

Creating Generator Package

Let’s get started with opening Xcode and creating a new Swift package.

Adding Swift Argument Parser

Apple has released the Swift Argument Parser package a while ago which makes writing CLI applications in form of Swift packages a joy. It implements a lot of boilerplate code and does important groundwork for you. All the basics are covered.

I updated my project’s package description to include the Swift Argument Parser as a dependency and in the executable product. Then I replaced the package’s main boilerplate code with a minimal ParsableCommand implementation which takes a file argument and prints it.

import ArgumentParser

@main
struct Mogelei: ParsableCommand {
    @Argument(help: "The source code file to scan for protocols.")
    var file: String

    mutating func run() throws {
        print("Should scan file: \(file)")
    }
}

But wait, running the generator scheme in Xcode only yields the automatically generated usage information by the Swift Argument Parser package.

Error: Missing expected argument '<file>'

USAGE: mogelei <file>

ARGUMENTS:
  <file>                  The source code file to scan for protocols.

OPTIONS:
  -h, --help              Show help information.

Program ended with exit code: 64

Of course, the file argument is required and must be provided. This is convenient to do define in the scheme Xcode generated automatically for the target. Note that the path displayed in the screenshot is only an example.

Screenshot of Xcode's scheme editor

The file ExampleProtocolsToMock.swift contains this simple protocol:

protocol Person {
    func greet()
}

Reading and Parsing

Reading the file is easy but what about parsing Swift code? There is another Swift package by Apple which does the heavy lifting for us: SwiftSyntax. We do not need to reinvent the wheel and parse the source code by ourselves. SwiftSyntax does that for us. And even more: It also generates the mock code with result builders. We will get back to the latter later.

With this updated run() function of our command we already obtain a syntax tree for ExampleProtocolsToMock.swift:

mutating func run() throws {
    guard let data = FileManager.default.contents(atPath: file) else {
        throw MogeleiError.dataRetrieval
    }

    guard let code = String(data: data, encoding: .utf8) else {
        throw MogeleiError.stringConversion
    }

    let tree = Parser.parse(source: code)
    print(tree.description)
}

Looking for Protocols

Now the protocols to mock need to be filtered out. Even though ExampleProtocolsToMock.swift contains only that simple example protocol, in reality most Swift source code files contain other structures as well. For simplicity, let’s assume we want to mock all protocols we find and they are declared on the top level. This is achieved by extending the main run() function like this:

var declarations = [ProtocolDeclSyntax]()

for statement in tree.statements {
    guard let declaration = statement.item.as(ProtocolDeclSyntax.self) else {
        continue
    }

    declarations.append(declaration)
    print("Found declaration of: \(declaration.identifier)")
}

Drafting Mock Code

Because it serves as an example only the generated mock code is rather simple. To support all the bells and whistles it of course takes more than what I am going to show. Let us assume this is the mock we want to have for now:

class PersonMock {
  var greetCalled = false

  func greet() {
    greetCalled = true
  }
}

Then we can implement the generation of that code like this:

let mockCode = SwiftSyntaxBuilder.SourceFile {
        for declaration in declarations {
            ClassDecl(identifier: "\(declaration.identifier.text)Mock") {
                for member in declaration.members.members {
                    if let function = member.decl.as(FunctionDeclSyntax.self) {
                        let callCountVariableName = "\(function.identifier.text)Called"
                        VariableDecl(stringLiteral: "var \(callCountVariableName) = false")

                        FunctionDecl(
                            identifier: function.identifier,
                            signature: function.signature
                        ) {
                            SequenceExpr() {
                                ExprList() {
                                    IdentifierExpr(stringLiteral: callCountVariableName)
                                    AssignmentExpr(assignToken: Token.equal)
                                    BooleanLiteralExpr(booleanLiteral: true)
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    print(mockCode.formatted().description)

Yet it already becomes a lot of complicated boilerplate code to read even with result builder syntax. But maybe it also is my currently still lacking experience with the result builders in depth beyond SwiftUI. I am convinced there is a way to split up and structure complex builders. Note that I use string literals for convenience and because in those specific cases they are safe.

Throwing that into an Xcode playground and these few lines below proves the code is actually working.

// Generated mock code redacted from here for readability.

let person = PersonMock()
print("Has this person been greeted yet? \(person.greetCalled)")
person.greet()
print("And now? \(person.greetCalled)")

Conclusion

This is the point where the proof-of-work is achieved and the basic concept has become clear. Now the most important question is: How should the mocks work? Or in other words: what developer experience and features should they offer? The answers guide the further implementation. Beyond that I can imagine that it will become a tricky project once generics are involved.

As already mentioned: I have set up an example project and published it on GitHub. The individual commits correspond to the following steps above.

About The Author

Peter Thomas Horn is a professional software developer at Open-Xchange specialized on the Apple platform. He previously worked a decade across the full stack of various web technologies. Originally started with Java on Windows at the age of 12 years. While staying humble in throwing around buzzwords like "VR" and "machine learning" he occasionally experiences problems and the fitting solutions considered worth sharing.