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? Definitely the answer is no. However, unit testing often sounds  very scary for those developers who never tried it. The very first step on the road of code testing, most certainly, would be searching for examples and existing tools that help avoid writing boilerplate code.

 

For sure, as a smart developer, you can follow a DRY principle and skip reinventing the wheel. Rather select the library from the top ten search results, with the biggest count of Github stars and, what’s more important, goosebump feeling – “oh my precious”.

 

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

 

Swift was designed to be safe – supporting readonly reflection. Thus, there’s no way to modify your program at runtime. Overall, it’s good, code is executed as expected and other components have no possibility to 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 own 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 that we don’t owe.

 

Overview

 

For demonstration purpose of each tool, lets create simple protocol and class that adopts to 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 test for verifying 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 library for Swift appeared on Github in early 2016 and is still supported and updated. Also, it has the highest Github rating. Community is using it, so we give it a try.

 

Install options: Cocoapods, Carthage.

 

Additional setup: Cuckoo installation process also requires adding a new run script into your test target build phases. Include  in this script absolute path to each file and the framework will generate mocks. Import generated file into the project once, and then re-generating mocks will 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 absolute file path into script, like in the example.

"${INPUT_DIR}/FileName.swift"

 

Documentation: Guide for installation is full, number of usage examples is not so many, but enough.

 

Features: Cuckoo’s set of type matchers cover basic value types, or you can extend any class by conforming protocol Matchable yourself. Also, there are call and parameter matchers for verification of 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, 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 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, but 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 test target. Generated code is put into one file that grows tremendously with your project. The only reason to edit it could be import of external frameworks.

 

SwiftyMocky

 

Lightweight framework for mocking with an idea to automatically mock Swift protocols. It has dependency 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, setup SwiftyMocky CLI on your working machine. Second,  add it into the project’s test target via one of 3 options listed above. Open Terminal in your project root folder and execute next commands one by one:

 

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

 

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

 

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

 

Documentation: Guide for install is full, documentation for usage is great, it covers lots of cases.

 

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

 

Live status: Based on commits, 2 guys are doing real thing, updates&fixes are pushed once a month, 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, how to install lib, by which tool generate mocks, 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 a real project is easy.

 

SwiftMockGenerator

 

Spoiler! It’s not a library, but rather a plugin to Xcode that helps to solve exactly same problem – generating test doubles. SwiftMockGeneratorForXcode is an exception in this list of frameworks. Instead of being integrated into project this generator can only be added as an extension to Xcode. Also, you won’t have any custom verifiers or matchers, do all assertion using 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 project required.

 

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

 

Documentation: Fully described guide how to install, set of examples with explanations is available on Github page.

 

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

 

Live status: At the moment of writing article, this plugin looks like one of the most frequently updated. Already works on newly launched Xcode 11. The author is open to new features 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 awesome advantages – no coupling between code and tests at all, good variety of supported test doubles, and it seems like project won’t stop on current results and will move forward. Among disadvantages worth noting, there is no support for generics and sometimes generation process is really slow.

 

Conclusion

 

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

 

Well known Uncle Bob keeps thought not using them as much as possible, he says: “The point at which you start to really need a mocking framework is the very point at which the coupling between your tests and code is getting too high. 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 no. We believe there is no wrong choice if it leads to code improvement and tests quality, stability and reliability – that’s the main priority. It’s always good to have an option, but the final decision is up to you.