Testing consumers of WCSession with protocols in Swift.

matthew
4 min readAug 13, 2015

Singletons can be a pain. More to the point, testing code which makes use of singletons can be a complete and utter pain. Unfortunately for those of us determined to practice TDD in the course of Swift development (and, indeed, those of us who painstakingly write tests afterwards), singletons are also a fairly common sight throughout Apple frameworks.

As such it was with little surprise that I learned that WatchConnectivity, the framework which handles all communications between iOS 9 and watchOS 2, relies on a single shared instance of one class: WCSession.

Accessing WatchConnectivity’s WCSession singleton

One of the main problems when it comes to testing code which involves singletons in Swift comes when you want to, for example, verify that a specific method was called on the singleton.

Code like this is as good as untestable:

Hideous, untestable code

Okay, you can probably verify that the delegate was set properly (though you could just as easily run into problems given that the same defaultSession() will potentially be shared between all tests), but there’s no way of confirming from here that activateSession() is called. Even if we were somehow able to track the effect of calling activateSession(), this test would then be dependent on the implementation of a class we didn’t write and — just as importantly — one that is not part of the unit under test.

Generally, developers know to improve the testability of code like the above as follows:

Uncle Bob would be proud

Better, right? Clearly injecting the session in means we have significantly more control over the object we pass into the method from our test case.

In theory this should be the only modification needed in our production code to make it more testable. Stubbing in Swift is easy, and so we should just be able to do something akin to the following:

(Mis)Using inheritance to stub out methods

It’s perfectly reasonable to do this, and in the majority of cases stubbing classes like this is fine. However, in the case of many singletons — WCSession included — this is not a viable solution. This is because when we try to init our lovely stubbed singleton, we find that its parent class has no public initialisers. We can’t even write our own, because we’re unable to override the forbidden init method.

Perhaps the next best thing to try is to call defaultSession() on our MockWCSession class, but this method — the implementation of which we have no real idea about — returns a WCSession object, even when called on MockWCSession.

Perhaps it might be possible to cast the WCSession returned by defaultSession() to an instance of our mock subclass? Indeed, this is exactly what I tried under the same circumstances, and it sort-of worked. However I then started to notice tests passing when I ran them by themselves, but failing when I ran them as part of a whole suite. As mentioned above, this was probably because the singleton was maintaining state between tests.

Attempting to actually get at our mock

This could perhaps be avoided by attempting to completely reset the defaultSession() in XCTestCase’s tearDown() method, but that’s a pretty ugly solution, and is very dependent on the implementation of WCSession, over which we have no control.

So if we can’t create an init method, and trying to cast defaultSession() is causing ugly problems, what have we got left?

The answer, as I suspect will be the case more and more often as Swift matures, is to use a protocol.

To see how a protocol helps to solve the problem, let’s define one called WatchConnectingSession as follows:

Am I a protocol oriented programmer yet?

From here we can clearly do the following:

Uncle Bob would be even prouder. Maybe.

This is near identical to the implementation of this method which we had above, with only the type of the session object being different. So how does this help testabilty? Because it allows us to do this:

Tests. Beautiful, beautiful tests.

Suddenly we have complete control over the object being passed into our tests, and we can be completely certain about its state at any point in the code.

But, I hear you cry, that doesn’t really solve the problem — we needed to use this method to set up our WCSession, and WCSession doesn’t conform to WatchConnectingSession!

Boom.

Yes it does.

This is entirely down to the way we defined the protocol above. Feel free to check it against the documentation for WCSession. The method signatures of our protocol are identical to those in the interface of the WCSession class, so conformance is trivial (though, to be clear, it does need to be stated explicitly somehere in the code).

From here, as you find yourself using more methods of WCSession, you can simply extend the protocol with the correct method signatures, and stub out those methods in some sensible way in your mock.

And there you have it: a simple way to make ugly singletons more testable.

--

--

matthew

attempting to impress by affecting greater importance, talent, culture, etc., than is actually possessed. ios developer & general person at FanDuel.