Parrot: A Swift Mock Generator

Compile-time, protocol-driven mock generation.

Posted by Brett Lindsay on December 19, 2018

The Problem

When I started iOS and Swift programming professionally, I was coming from a team where we were learning test-driven JavaScript web development with Node and React. Chai, Mocha, and Sinon were the standard set of unit testing tools for my team at the time. While running tests and writing assertions in Swift and Xcode are similar enough, the contrast was painfully obvious when stubbing out functionality. Since Swift has no run-time reflection, Xcode provides no tools to help stub out functionality. The typical pattern for enabling stubbed out functionality involves:

  • Writing a protocol for the class or struct to be mocked
  • Injecting that protocol as a dependency
  • Writing a mock class conforming to that protocol
  • And finally, injecting an instance of the mock into the class or struct being tested.

These steps are then duplicated many times because many classes and structs have multiple dependencies that each require their own mocks. Clearly, stubbing out functionality in Swift takes quite a few more steps compared to sinon.stub().

Some other issues also present themselves quickly once you start modifying others’ projects with unit tests and even just modifying your own tests and code.

  • Multiple contributors in a code base means mock classes could have inconsistent manual implementations. Some will have a Boolean myFunctionCalled variable, while others will have an Integer myFunctionCallCount variable; one could use a tuple lastParametersCalledWith vs. an array of tuples, myFunctionParameters or myFunctionCalledWith. Without checks or automation, developers don’t always hold themselves or others accountable to their own rules, patterns, or standards that help speed up development.
  • Any changes to a protocol means fixing compile errors for their mocks.
  • Poorly written mocks could be missing expected properties of mocks completely

There Has to Be a Better Way

Parrot started out as two separate attempts at the same problem which ended up not overlapping at all. I had written a rough script to find the first protocol definition in a given file and psuedo-parse the code in order to generate a mock. It was a bare-bones Swift script (I personally really like scripting with Swift). A friend in my group of developers had started writing code that would scan a directory recursively and identify the Swift files. It was exciting realizing what we could make together. We took a couple days putting the pieces together, and we had our first pass at a Swift mock generator. The initial implementation definitely had some glaring holes in it, and it certainly isn’t “finished” today. Even so, the benefits are evident. Generating your mocks with Parrot guarantees they will all be written in the same format, with the same interface to assert against in tests, and there won’t be any missing implementations. It’s meant to generate before the compile time of your tests, so any protocol changes in the project will be reflected in your mock definitions automatically.

See How It Works

Adding Parrot to a Project

This and more details about usage can be found in the Parrot README.

Other Options

When we started Parrot, there were not similar tools to be found and the ones that did exist had taken directions we didn’t want to follow. At the time of writing this post, another option exists: Swift Mock Generator for Xcode. It’s used as an Xcode extension. Readers should feel free to compare and contrast these two tools.

Contribute

Parrot is far from perfect and has room for enhancements. From what I’ve seen, the majority of generated mocks work right out of the box while the ones that don’t only need a small amount of modification. I hope Parrot makes Swift testing a much better experience for all who use it, and I’m looking forward to its future enhancement. https://github.com/MonsantoCo/Parrot

posted on December 19, 2018 by
Brett Lindsay