Swift Mocking Tools. To Use, or Not to Use, That Is the Question

Swift Mocking Tools. To Use, or Not to Use, That Is the Question

Could you feel confident about the quality of your code without covering it with unit tests? The answer is no. However, unit testing often sounds scary for developers who have never tried it, especially in the Swift Mocking Tools. So, the very first step on the road of code testing, most certainly, would be searching for examples and tools that help avoid writing boilerplate code.

As a savvy developer, you can follow the DRY principle and skip reinventing the wheel. Instead, select the library from the top ten search results with the enormous number of Github stars and, more importantly, a goosebump feeling—“Oh my precious.“

Everything would be smooth and easy, except that Swift is not Objective-C. Supplying test doubles is incredibly easy in Objective-C, where the central mechanism is messages. You could send any message to any object. If you had previous experience mocking Objective-C using OCMock or OCMockito, forget it.

Swift was designed to be safe, supporting read-only reflection. Thus, there’s no way to modify your program at runtime. Overall, it’s good; the code is executed as expected, and other components cannot change it. But leading back to our topic, all mocking frameworks are built on reflection to be able to change classes, types, and objects at runtime.

This language needs its superheroes to write testable code, and we know them well—protocols and extensions! No matter what implementation it is—class, enum, or struct—the purpose of protocols remains the same: to define abstractions and add new functionality to types, even ones we don’t owe.

Overview

For the demonstration purposes of each tool, let’s create a simple protocol and class that adopts it with empty implementation.

protocol AuthorizationServiceType {
    func login(email: String, password: String)
}class AuthorizationService: AuthorizationServiceType {
    
    func login(email: String, password: String) {
        // implementation goes here
    }
    
}

Also, depending on this protocol, we need one more class. In tests, it will be our SUT.

class LoginPresenter {
    
    private var authorizationService: AuthorizationServiceType
    
    init(authorizationService: AuthorizationServiceType) {
        self.authorizationService = authorizationService
    }
    
    func login(email: String, password: String) {
        authorizationService.login(email: email, password: password)
    }
}

We will examine how to write a test to verify the invocation of the function login with the correct input parameters using each tool.

Years ago, one author said, „Swift is the Only Modern Language without Mocking Frameworks.“ Let’s find out what has changed so far.

Cuckoo

One of the first mocking libraries for Swift appeared on GitHub in early 2016 and is still supported and updated. Also, it has the highest Github rating. The community is using it, so we give it a try.

Install options: Cocoapods, Carthage.

Additional setup: The cuckoo installation process requires adding a new run script into your test target build phases. Include the absolute path to each file and the framework that will generate mocks in this script. Import the generated file into the project once, then re-generate it to keep it current. The class name starts with ‚Mock‚ — you can use each test double as mock, stub, or spy.

Mark protocol to mock: Add an absolute file path into the script, like in the example.

"${INPUT_DIR}/FileName.swift"

Documentation: The installation guide is complete; the number of usage examples is not large, but enough.

Features: Cuckoo’s type matches cover basic value types, or you can extend any class by conforming to protocol Matchable yourself. Also, there are call and parameter matches to verify method invocation or return value. It supports generic protocols, classes, and methods. It’s not so important, but there is even support for Objective-C.

Live status: The community is active, updates and fixes are pushed often, and everyone is invited to contribute.

.

Test example:

func testLoginUsingCuckoo() {
        //Given
        let email = "test@gmail.com",
        password = "supersecure"
        
        let mock = MockAuthorizationService()
        let sut = LoginPresenter(authorizationService: mock)
        
        mock.enableDefaultImplementation(AuthorizationService())
        //In Cuckoo its required either provide default implementation or stub parameter/method
//        stub(mock) { stub in
//            when(stub.login(email: email, password: password)).thenCallRealImplementation()
//        }
        
        //When
        sut.login(email: email, password: password)
        
        //Then
        verify(mock).login(email: email, password: password)
    }

Please do not forget to add stub or default implementation, otherwise the test will fail.

Summary: This library is simple and greatly helps with mocking. Although adding classes to be mocked is inconvenient, being a con simultaneously is a big pro of Cuckoo since there is a very low dependency on your code, and all work can be done inside the test target. Generated code is put into one file that grows tremendously with your project. The only reason to edit it could be an import of external frameworks.

SwiftyMocky

Lightweight framework for mocking with an idea to mock Swift protocols automatically. It depends on the source used for scanning code and generating mocks.

Install options: Cocoapods, Carthage, SPM.

Additional setup: SwiftyMocky has a 2-step installation. First, set up SwiftyMocky CLI on your working machine. Second, add it to the project via one of the three options listed above. Open Terminal in your project root folder and execute the following commands one by one:

> swiftymocky setup     # if you don't have a Mockfile yet
> swiftymocky doctor    # validate your setup
> swiftymocky generate  # generate mocks

The result of the setup command is a Mockfile, which can be edited for advanced settings. Use the command doctor to verify the setup status before generating. On the first command call, the generated file should be imported into a project. Then, each time, mock generation will update the file. Every generated test double can be used as a mock or stub.

Mark protocol to mock: Option 1: Add conformance to the protocol you want to mock by AutoMockableOption 2: Before your protocol, add annotation //sourcery: AutoMockable.

Documentation: The installation guide is complete, and the usage documentation is great and covers many cases.

Features: The same as in Cuckoo: matches, wrapped parameters, and the option to act as a stub call. Matching custom classes requires registration and implementation of the Equatable protocol.

Live status: Based on commits, two guys are doing the real thing, updates and fixes are pushed once a month, and there are few good tutorials on library usage.

Test example:

func testLoginUsingSwiftyMocky() {
        //Given
        let email = "test@gmail.com",
        password = "supersecure"
        
        let mock = AuthorizationServiceTypeMock()
        let sut = LoginPresenter(authorizationService: mock)
        
        //When
        sut.login(email: email, password: password)
        
        //Then
        Verify(mock, .login(email: .value(email), password: .value(password)))
        //OR
        //mock.verify(.login(email: .value(email), password: .value(password)))
    }

Summary:

You always have a choice of how to install lib and by which tool to generate mocks, and you can do it manually or automatically. Even though additional dependency on Sourcery and the 2-step installation process might look too much, that’s that‘ ‚or th‘ standalone generation process. Usage of this library in an actual project is easy.

SwiftMockGenerator

Spoiler! It’s not Its Library but an Xcode plugin that helps solve the same problem—generating test doubles. SwiftMockGeneratorForXcode is an exception in this list of frameworks. Instead of being integrated into the project, this generator can only be added as an extension to Xcode. Also, you won’t have custom verifiers or matches; do all assertions using the XCTest framework.

Install options: The installation process is the same as for any program on Mac—drag to Applications and allow it to control Xcode in system preferences.

Additional setup: No additional integration into the project is required.

Mark protocol to mock: Create an empty class that will be your mock implementation, make an inheritance to protocols or classes that should be mocked, and put the cursor into the class declaration. From there, go to Xcode Editor -> Mock Generator, selecting one of 4 available options: spy, stub, dummy, or partial spy.

Documentation: The GitHub page contains a fully described guide on installing a set of examples with explanations.

Features: It supports Swift 5, can mock classes or protocols, and handles closures, throws, and failable initializes. However, it has limitations, like supporting properties with inferred type; generics support is not yet done. Also, generating test doubles could be bound to keyboard shortcuts.

Live status: When writing the article, this plugin looked like one of the most frequently updated. It already works on the newly launched Xcode 11, and the author is open to new feature requests.

Test example:

func testLoginUsingSwiftMockGenerator() {
        //Given
        let email = "test@gmail.com",
        password = "supersecure"
        
        let mock = SpyAuthorizationService()
        let sut = LoginPresenter(authorizationService: mock)
        
        //When
        sut.login(email: email, password: password)
        
        //Then
        XCTAssertTrue(mock.invokedLogin)
        guard let parameters = mock.invokedLoginParameters else {
            XCTFail("No parameters")
            return
        }
        XCTAssertTrue(parameters == (email: email, password: password), "Wrong parameters")
    }

Summary: This solution has tremendous advantages—no coupling between code and tests at all, a good variety of supported test doubles, and it seems like the project won’t have current results and will move forward. The disadvantages worth noting are that there is no support for generics, and sometimes, the generation process is slow.

Conclusion

We, as developers, do not have many tools for mocking on Swift, and there are some strict boundaries due to language-limited runtime access. So it’s essential to mention one more obvious option—writing test doubles independently, without any tools. Here, we come to the critical question of whether or not to use external frameworks for mocking.

Well-known Uncle Bob recommends not using them as much as possible: „The point at which you start to need a mocking framework is the point at which the coupling between your tests and code is getting too high. After that, however, you should strive to keep the coupling between your code and tests low enough that you don’t need to use the mocking framework very often.“

In Evolice, we also have different thoughts and personal preferences about using Swift Mocking Tools or not. We believe there is no wrong choice if it leads to code improvement and tests quality, stability, and reliability — that’s why. Of course, having an option’s a little good, but the final decision is yours.