blob: 2a10ce6109e00a71494b3ca25358c267c1cbbee0 [file] [log] [blame] [edit]
---
layout: default
title: Using OCMock with Swift
tags: []
status: publish
type: page
published: true
nosidebar: true
comments: true
---
<h1>{{ page.title }}</h1>
<p>This page decribes my current understanding of using OCMock with Swift. It is updated as Swift evolves.
<p>Last updated for Xcode 7.
<h2>General FAQ</h2>
<h3>Will there be a mock framework for Swift written in Swift?</h3>
<p>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.
<h3>Do I even need a mock framework for Swift?</h3>
<p>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 <a href="http://blog.eliperkins.me/mocks-in-swift-via-protocols">blog post by Eli Perkins</a> and another <a href="http://www.jessesquires.com/testing-without-ocmock/">blog post by Jesse Squires</a> 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 <a href="http://www.mockobjects.com/2007/04/test-smell-everything-is-mocked.html">post by Steve Freeman</a>) already outlined similar ideas; they were by no means pushing to use mocks for everything.
<p> 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.
<h3>Can I use OCMock using the language bridge functionality?</h3>
<p>Yes, but with limitations. If you are brave. It's unlikely that OCMock will ever fully support Swift.
<h2>Using Swift with OCMock</h2>
<p>The code in this section is taken from the <a href="https://github.com/erikdoe/ocmock/tree/master/Examples/SwiftExamples">SwiftExamples</a> project in the OCMock repository.
<h3>Creating a mock for a Swift class</h3>
{% highlight swift %}
// class in Swift
class ServerConnection : NSObject {
func fetchData() -> String {
return "real data returned from other system"
}
}
{% endhighlight swift %}
{% highlight objc %}
// test in Objective-C
- (void)testMockingAnObject
{
id mock = OCMClassMock([ServerConnection class]);
OCMStub([mock fetchData]).andReturn(@"stubbed!");
{% endhighlight objc %}
<p>As long as the Swift class inherits from <code>NSObject</code> it is possible to create a mock for it. Instance methods can be stubbed.
<p>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.
<h3>Using the mock with another Swift object</h3>
{% highlight swift %}
// 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();
}
}
{% endhighlight swift %}
{% highlight objc %}
// 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.");
}
{% endhighlight objc %}
<p>It is possible to use the mock with a Swift object. Starting with Swift 2 it <a href="https://github.com/erikdoe/ocmock/commit/a46478f8dd0aee60517868f3964f95103a2a3f18">seems</a> that the Swift object must inherit from <code>NSObject</code>.
<p>However, note the declaration of the <code>connection</code> variable in the controller class. Unfortunately, it is necessary to use a protocol. Changing the type from <code>Connection</code> to <code>ServerConnection</code> will make the test crash.
{% highlight swift %}
// 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"
}
}
{% endhighlight swift %}
<h3>Creating a partial mock for a Swift object</h3>
{% highlight objc %}
// 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.");
}
{% endhighlight objc %}
<p>Partial mocks can be created for Swift objects that inherit from <code>NSObject</code>.
<p>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 <code>fetchData</code> method has been called.
<h3>Known limitations</h3>
<ul>
<li>Tests have to be written in Objective-C
<li>Objects that should be mocked must inherit from <code>NSObject</code>
<li>References to objects that are replaced with a mock must use protocols
<li>No stubbing/expecting/verifying of class methods
</ul>