I’ve become a fan of creating one’s own test doubles (aka mock objects) instead of using a mock framework like OCMock or OCMockito. Uncle Bob describes this process in one of his Clean Code videos, and it seems to work very well in Swift.
Why create your own mock objects? There are a couple reasons for doing this.
- Tests can be more readable.
I say “can” because its really up to you to create readable mocks. - You have nearly unlimited faking capability.
I say “nearly” because there are some things that may not be possible using class inheritance, for example overriding methods or properties that have been marked as “final“.
So let’s take a look at using manually created fake doubles for testing a simple Power class. This class has one purpose: to report whether or not the device is plugged-in. The way this is done on an iOS device is by using the batteryState property of the UIDevice currentDevice singleton.
Uh-oh, did I just say “singleton”? Yep. That’s typically bad news for unit testing. However, this won’t be a problem for us because we’re going to replace the UIDevice singleton with our own fake object. This means that we’ll have to follow the “tell, don’t ask” principle, and pass in the UIDevice object to our Power object instead of letting it “ask” for the singleton UIDevice currentDevice object.
class Power { var device: UIDevice init() { self.device = UIDevice.currentDevice() } init(usingMockDevice mockDevice: UIDevice) { device = mockDevice } func isPluggedIn() -> Bool { let batteryState = self.device.batteryState let isPluggedIn = batteryState == UIDeviceBatteryState.Charging || batteryState == UIDeviceBatteryState.Full return isPluggedIn } }
Here we’re using separate initializer methods to allow passing in a fake UIDevice object, or default to using the singleton currentDevice. Normal production code usage would look something like this:
var power = Power() ... if power.isPluggedIn() { ...
On the other hand, for testing this we’ll create a test double and use it with the usingMockDevice initializer as shown here:
class mockPluggedInDevice: UIDevice { override var batteryState: UIDeviceBatteryState { return UIDeviceBatteryState.Full } }
class PowerTests: XCTestCase { func testPluggedInWhenBatteryFull() { let mockDevice = mockPluggedInDevice() let power = Power(usingMockDevice: mockDevice) XCTAssertTrue(power.pluggedIn()) } }
So here’s a question for you. In which file do we put the mockPluggedInDevice code? I’m a bit conflicted on this. On one hand, I usually prefer to keep test code completely out of product code files. But on the other hand, mock objects are by nature going to be pretty dependent on the product code they are mocking, so I should probably keep it with the product code.
Clarification: For simplicity, I am using the term “mock” object to describe what would more accurately be called a “stub”. Martin Fowler clearly defines the difference in this 2007 article.
Suggestion on your question re: where to put things. Use an extension in your test suite to add the test initializer that takes your mock. Define your mock class in the test itself (I think swift let’s you even define classes inside methods, no?).
I think this is sensible. Instead of polluting the test module’s global scope with mock classes which aren’t used throughout the app anyway, I add them to the test’s scope instead. I end up with slightly duplicate mock classes from time to time, but at least I don’t have to worry about a one-size-fits-all mock solution which does many things for many tests.