Mocking With Protocols in Swift

Let’s get right to it. You need to test your code, and you need to test it often. You do a lot of manual testing throughout the development process, find bugs, and fix them. While this can be very beneficial, it leaves much of the code untested. When I say untested, I mean untested by you. The code will be tested at some point, it just might be by one of your users. This is where writing automated unit tests comes in, however, it is often the last thing developers do, if at all. Where do you start? How do you make this class testable? Many of these challenges can be overcome by using protocols to mock our objects in testing.

When you are writing testable code, there are important characteristics your code should adhere to. First, you need to have control over any inputs. This includes any and all inputs that your class acts on. Second, you need visibility into the outputs. There needs to be a way to inspect the outputs generated by your code. Your unit tests will use the outputs to validate things are working as expected. Lastly, there should be no hidden state. You should avoid relying on internal system state that can affect your code’s behavior later. Using protocols can help to meet these characteristics.

Mocking with Protocols

Mocking is imitating something or someone’s behavior or actions. In automated software testing it is creating an object that conforms to the same behavior as the object it is mocking. Many times the object you want to test has a dependency on an object that you have no control over. There are several ways to mock this object which you depend on. One way is to subclass it. With this approach you can override all the methods you use in your code for easy testing, right? Wrong. Subclassing many of these objects come with hidden difficulties. Here are a few.

Unknown state: you don’t know if your object has any shared owners which can result in one of them mutating the expected state of your mock.

Also, in Swift structs are powerful and useful value types. Structs, however, cannot be subclassed. If subclassing is not an option, then how can the code be tested?

Protocols! In Swift, protocols are full-fledged types. This allows you to set properties using a protocol as it’s type. Protocols with testing overcome many of the difficulties that come with subclassing code you don’t own and the inability to subclass structs.

Mocking Example

In the example, you have a class that interacts with the file system. The class has basic interactions with the file system, such as reading and deleting files. For now, the focus will be on deleting files. The file is represented by a struct called MediaFile which looks like this.

struct MediaFile {
var name: String
var path: URL
}

The FileInteraction struct is a convenience wrapper around the FileManager that allows easy deletion of the MediaFile

All of this is managed by the MediaManager class. This class keeps track of all of the users media files and provides a method for deleting all of the users media. deleteAll method returns true if all the files were deleted. Any files that are unable to be deleted are put back in the media array.

This code, as it stands, is not very testable. It is possible to copy some files to the directory, create the MediaManager with MediaFiles that point to them, and run a test. This, however, is not repeatable or fast. A protocol can be used to make the tests fast and repeatable. The goal is to mock the FileInteraction struct without disrupting MediaManger. To do this, create a protocol with the delete method signature and declare the FileInteraction conformance to it.

There are two changes to MediaManager that need to be implemented. First, the type of the fileInteraction property needs to be changed. Second, add an init method that takes a fileInteraction property and give it a default value.

Summary

Initially the MediaManager delete all method was not very testable. Using a protocol to mock interaction with the file system made testing this code repeatable and fast. The same principles for testing the delete all method can be applied to other areas of interaction such as reading, updating, or moving files around. Protocols are powerful tools for testing code. They can also be used to mock Foundation classes such as URLSession and FileManager where applicable.