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 by unit tests? The answer is no. However, unit testing often sounds scary for those developers who 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 a DRY principle and skip reinventing the wheel. Instead, select the library from the top ten search results with the enormous count 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. In case you had previous experience mocking on 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 on 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 — 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, we need one more class, depending on this protocol. 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 take a look at how to write a test for verifying the invocation of function login with correct input parameters using each tool.

As one author said years ago, “Swift: 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, and then re-generate it to keep it up to date. Class name starts from the word ‘Mock’ — each test double you can use 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 guide for installation is complete; the number of usage examples is not so many, but enough.

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

Live status: community is active, updates&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 in use and it greatly helps with mocking. Although the way of adding classes to be mocked is inconvenient, being a con, at the same time it is a big pro of Cuckoo since there is a very low dependency in 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 automatically mock Swift protocols. It depends on Sourcery, which is 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’s test target via one of the 3 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 Mockfile, which can be edited for advanced settings. Use command doctor to verify 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 you can use 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 guide for installation is complete, documentation for usage is great, and it covers lots of cases.

Features: Same as in Cuckoo, matchers, wrapped parameters, option to act as stub call. Matching custom classes requires registration and implementation of Equatable protocol.

Live status: Based on commits, 2 guys are doing the real thing, updates&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, by which tool to generate mocks, and do it manually or automatically. Even though additional dependency on Sourcery and 2 step installation process might look too much, that’s a ‘price’ for the standalone generation process. Usage of this library in an actual project is easy.

SwiftMockGenerator

Spoiler! It’s not a 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 any custom verifiers or matchers, do all assertions using the XCTest framework.

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

Additional setup: No additional integration into the project is 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: A fully described guide on installing a set of examples with explanations is available on the Github page.

Features: It supports Swift 5, can mock classes or protocols and handles closures, throws, and failable initializers. Currently, it has limitations like the support of 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 looks one of the most frequently updated. It already works on the newly launched Xcode 11. 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 stop on current results and will move forward. Among the disadvantages worth noting, there is no support for generics, and sometimes the generation process is slow.

Conclusion

Not so many tools we, as developers, have for mocking on Swift, with some strict boundaries due to language limited runtime access. So it’s essential to mention one more obvious option — writing test doubles on your own, without any tools. And here we come to the critical question — use or not to use external frameworks for mocking.

Well-known Uncle Bob keeps not using them as much as possible; he says: “The point at which you start to need a mocking framework is the very 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 Evolvice, we also have different thoughts and personal preferences about using tools for mocking or not. We believe there is no wrong choice if it leads to code improvement and tests quality, stability, and reliability — that’s the main priority. Of course, it’s always good to have an option, but the final decision is up to you.