Mocking in Swift

Unit TestingMockingSwift

Unit Testing Problem Overview


How do you mock an object in Swift?

The Mirror protocol sounded promising, but it doesn't do much right now.

So far the only approach I found is to subclass and override all methods of the mocked class. This is of course not a true mock, far from ideal, and a lot of work.

Any other ideas?

Why not OCMock?

From the source:

> Can I use OCMock using the language bridge functionality? > > Yes, but > with limitations. If you are brave. As of now this is highly > experimental. There's no guarantee that OCMock will ever fully support > Swift.

Known limitations:

  • Tests have to be written in Objective-C

  • Objects that should be mocked must inherit from NSObject

  • No stubbing/expecting/verifying of class methods

Unit Testing Solutions


Solution 1 - Unit Testing

NSHipster touches on language features in Swift which make an external mocking library less necessary:

> In Swift, classes can be declared within the definition of a function, allowing for mock objects to be extremely self-contained. Just declare a mock inner-class, override and [sic] necessary methods:

func testFetchRequestWithMockedManagedObjectContext() {
    class MockNSManagedObjectContext: NSManagedObjectContext {
        override func executeFetchRequest(request: NSFetchRequest!, error: AutoreleasingUnsafePointer<NSError?>) -> [AnyObject]! {
            return [["name": "Johnny Appleseed", "email": "[email protected]"]]
        }
    }
    
    ...
}

The ability to create a subclass of your external dependency in the local scope plus the addition of XCTestExpectation solve a lot of the same problems as OCMock.

The one thing that a library such as OCMock provides that is very useful are its "verify" methods to ensure that the mock classes were called. It's possible to manually add this, but the automatic addition is nice.

Solution 2 - Unit Testing

I create my mock classes by wrapping everything in a protocol. I hand roll a mock class to conform to the protocol in question, like so:

protocol Dog: class {
    var name: String { get }

    func bark()
}

class DogImpl: Dog {
    var name: String

    init(name: String) {
        self.name = name
    }

    func bark() {
        print("Bark!")
    }
}

class DogMock: Dog {
    var name = "Mock Dog"
    var didBark = false

    func bark() {
        didBark = true
    }
}

I use this in conjunction with dependency injection to achieve full test coverage. It's a lot of boilerplate, but I haven't had any issues with this approach so far.

Regarding subclass mocking, you'll run into trouble with final classes, or if they have non-trivial initializers.

Solution 3 - Unit Testing

I want to point something in addition to marked answer - I do not know whether it's a bug or not.

If you subclass NSObject somehow(in my case I was subclassing UIView which internally subclass NSObject) you need to declare overriden function explicity with @objc otherwise your test wont compile. In my case the compiler itself crashes with following:

> Segmentation Fault: 11

So the following class:

public class ClassA: UIView
{
    @objc public func johnAppleseed() {
        
    }
}

Should be unit tested the following way:

class ClassATests: XCTestCase {
    
    func testExample()
    {
        class ClassAChildren: ClassA
        {
            @objc private override func johnAppleseed() {
                
            }
        }
    }
}

Solution 4 - Unit Testing

I recommend using Cuckoo, which can handle most standard mocking tasks.

Example Classes:

class ExampleObject {

    var number: Int = 0

    func evaluate(number: Int) -> Bool {
        return self.number == number
    }

}

class ExampleChecker {

    func check(object: ExampleObject) -> Bool {
        return object.evaluate(5)
    }

}

Example Test:

@testable import App
import Cuckoo
import XCTest

class ExampleCheckerTests: XCTestCase {

    func testCheck() {
        // 1. Arrange
        let object = MockExampleObject().spy(on: ExampleObject())
        stub(object) { object in
            when(object.evaluate(any())).thenDoNothing()
        }
        let checker = ExampleChecker()

        // 2. Action
        checker.check(object)

        // 3. Assert
        _ = verify(object).number.get
        verify(object).evaluate(any())
        verifyNoMoreInteractions(object)
    }

}

Solution 5 - Unit Testing

You can achieve this kind of mocking with MockFive.

The gist of it is that you have to create a mock 'by hand' of the source class that you want to mock--

but then, you can stub its methods dynamically, so you can use it wherever you need and configure its behavior there.

I wrote an article about how to use it here.

Solution 6 - Unit Testing

Because of the limitations you wrote, OCMock does not work very well in Swift (as every mocking framework strongly dependent on the runtime).

There are several mocking frameworks for Swift though, from semi-manual to almost fully automatic mock generation. Some of them are already listed in the answers, so I'll just recommend another one, which I'm one of authors.

https://github.com/MakeAWishFoundation/SwiftyMocky

I won't go much into details, it has its minor limitations, but from what I see it has widest set of features from Swift frameworks (at least the ones I know), including generics support, @objc members, and updating mocks while you write/change protocols (watcher mode).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhpiqueView Question on Stackoverflow
Solution 1 - Unit TestingDrew BeaupreView Answer on Stackoverflow
Solution 2 - Unit TestingMarkView Answer on Stackoverflow
Solution 3 - Unit Testinghris.toView Answer on Stackoverflow
Solution 4 - Unit TestingsundanceView Answer on Stackoverflow
Solution 5 - Unit TestingDaniel BurbankView Answer on Stackoverflow
Solution 6 - Unit TestingAndrzej MichniaView Answer on Stackoverflow