OCMock and Swift

This page decribes my current understanding of using OCMock with Swift. It is updated as Swift evolves.

Last updated for Xcode 7.

General FAQ

Will there be a mock framework for Swift written in Swift?

Maybe. As of now it doesn't look too likely, though, because mock frameworks depend heavily on access to the language runtime, and Swift only offers extremely limited access.

Do I even need a mock framework for Swift?

Yes and no. Swift has many useful language features that allow for more compact code, which should make creating mocks, stubs, and spies in Swift much less tedious than in Objective-C. A blog post by Eli Perkins and another blog post by Jesse Squires describe approaches to testing without a mock framework. Interestingly, the thinking of the people who pioneered mock frameworks and dependency injection over 10 years ago (see "Don't mock third-party libraries" in this post by Steve Freeman) already outlined similar ideas; they were by no means pushing to use mocks for everything.

That said, mock frameworks have proven convenient in many languages. It's not that they are essential, but they do add convenience. Stubbing a factory method to return a mock is quick and easy. Creating a protocol and a wrapper, and using dependency injection is probably more sustainable, but it is also more work and looks more complex. As ever, having options and making the right choice seems key.

Can I use OCMock using the language bridge functionality?

Yes, but with limitations. If you are brave. It's unlikely that OCMock will ever fully support Swift.

Using Swift with OCMock

The code in this section is taken from the SwiftExamples project in the OCMock repository.

Creating a mock for a Swift class

// class in Swift
class ServerConnection : NSObject {
    func fetchData() -> String {
        return "real data returned from other system"
    }
}
// test in Objective-C
- (void)testMockingAnObject
{
    id mock = OCMClassMock([ServerConnection class]);
    OCMStub([mock fetchData]).andReturn(@"stubbed!");

As long as the Swift class inherits from NSObject it is possible to create a mock for it. Instance methods can be stubbed.

The test is obviously written in Objective-C. It might be possible to write a Swift wrapper around the core OCMock functionality so that tests can be written in Swift.

Using the mock with another Swift object

// another class in Swift
class Controller: NSObject {
    var connection: Connection;
    var data: String;
    
    class func newController() -> Controller {
        return Controller()
    }
    
    override init() {
        self.connection = ServerConnection();
        self.data = "";
    }
    
    func redisplay() {
        data = connection.fetchData();
    }
}
// test in Objective-C
- (void)testMockingAnObject
{
    id mock = OCMClassMock([ServerConnection class]);
    OCMStub([mock fetchData]).andReturn(@"stubbed!");
    
    Controller *controller = [Controller newController];
    controller.connection = mock;
    
    [controller redisplay];
    
    OCMVerify([mock fetchData]);
    XCTAssertEqualObjects(@"stubbed!", controller.data, @"Excpected stubbed data in controller.");
}

It is possible to use the mock with a Swift object. Starting with Swift 2 it seems that the Swift object must inherit from NSObject.

However, note the declaration of the connection variable in the controller class. Unfortunately, it is necessary to use a protocol. Changing the type from Connection to ServerConnection will make the test crash.

// protocol needed to make above example work
@objc
protocol Connection {
    func fetchData() -> String
}

class ServerConnection : NSObject, Connection {
    func fetchData() -> String {
        return "real data returned from other system"
    }
}

Creating a partial mock for a Swift object

// using same Swift classes as above    
- (void)testPartiallyMockingAnObject2
{
    Controller *controller = [Controller newController];

    id mock = OCMPartialMock(controller.connection);
    OCMStub([mock fetchData]).andReturn(@"stubbed!");
    
    [controller redisplay];
    
    OCMVerify([mock fetchData]);
    XCTAssertEqualObjects(@"stubbed!", controller.data, @"Excpected stubbed data in controller.");
}

Partial mocks can be created for Swift objects that inherit from NSObject.

The sublassing mechanism used by OCMock seems to work, too. This allows OCMock to stub and verify methods on the real object. Note how we create the partial mock on the controller's existing connection, a Swift object. The controller keeps using a reference to the Swift object it has created, and OCMock can still verify that the fetchData method has been called.

Known limitations

Comments

Starting commenting system. This can take a moment.