You’re writing a unit test in Swift. You feel the need to avoid interactions with a real object. You want to provide a fake object, or a “test double” (like a stunt double). But Swift is strict about types! What do we do?[This post is part of the series TDD Sample App: The Complete Collection …So Far]
When should you use a test double / stub / mock / fake? For me, it comes down to needing two things:
I’m all about speed, because fast feedback is the main benefit of Test Driven Development. The “TDD Waltz” of Red-Green-Refactor is best when the waltz moves quickly. The faster you can iterate, the more you stay “in the zone” of enhanced productivity.
Reliability is absolutely necessary for unit tests. A test should pass, or it should fail. When a test is flaky, there’s often global state involved (like UserDefaults). Or there’s continually changing state (like Date).
So, speed and reliability drive the need for test doubles. An example of something that’s both slow and unreliable is networking.
Supplying test doubles is incredibly easy to do in Objective-C. Based on its Smalltalk roots, Objective-C’s central mechanism is messages. You can send any message to any object. (The object may or may not be happy about receiving the message.)
So if your code sends a quack message to an object, all that matters is that the object can quack. Objective-C’s types are suggestions that help us avoid a ton of errors. But you can defeat compile-time type-checking by casting an object to id.
There’s a danger that the interface of the real object can diverge from the fake. If your test sends a message that’s honored by the fake but unknown to the real thing, you’ll crash (with passing tests). This danger can be completely avoided, though. Mocking frameworks like OCMock and OCMockito use introspection to create fakes that model the real objects. If a method no longer exists in the real object, the mocking frameworks will complain. No divergence allowed!
In the Objective-C version of my Marvel Browser app, creating a fake version of NSURLSession is this easy:
It’s not actually an NSURLSession. But because it answers all messages that NSURLSession handles, who’s complaining?
Swift, on the other hand, will complain. There’s no cheating its strong type-checking. What are we to do?
If I’d started TDD in Objective-C, I might feel a little lost. Thankfully, I traveled a harder road. I learned Test Driven Development in C++.
Mock objects were first invented in Java. There, Java programmers initially relied on defining interfaces. Rather than programming to concrete instances, they could define interfaces instead. The interface could be supplied by either a real object or a fake one.
In C++, the way forward was Abstract Base Classes. I learned to define classes with pure virtual member functions. The implementations of these member functions could be supplied by either a real object or a fake one.
Java interfaces, C++ abstract base classes, Swift protocols. There are subtle differences, but their basic purpose is the same: to define abstractions that can be provided by concrete types.
(Of course, Objective-C has its protocols as well. They just didn’t come into play as necessities for testing.)
The uniformity of Swift’s protocols helps us write testable code. It doesn’t matter if the concrete implementation is a class, a struct, or an enum.
And thanks to extensions, we can supply additions to types, even standard ones. With an extension, we can retroactively apply a protocol to an existing class — even one we don’t own.
Let’s look at how protocols and extensions play together to give us a testing seam.
In the Swift version of the Marvel Browser TDD sample app, I want to start defining the FetchCharactersMarvelService. This object will fetch comic characters by sending network requests to the Marvel Comics API.
I prefer unit tests over integration tests. (Speed and reliability, remember?) So we want to write unit tests to drive the creation of this Service object. It will make a network request, which we don’t want in unit tests. What do we do?
To give us testing flexibility, the Service won’t determine its own URLSession. Instead, I’ll use Constructor Injection. The initializer will take an argument:
We want production code to provide an actual URLSession:
But test code should be able to provide something different — a different implementation of a protocol. Let’s define the protocol, initially empty:
Now we can use an extension to declare that URLSession conforms to this protocol:
With this, let’s change the type of the initializer argument from the concrete URLSession to the protocol:
Because URLSession conforms to our new URLSessionProtocol, the following production code will still be valid:
So now, all the test code needs to do is supply an implementation of URLSessionProtocol for testing. In the next article, we’ll start playing with ways to do this.
Let’s summarize by pulling all the code together, so we can see it all. Here’s the production code for an initializer that can take a URLSession:
Next time, we’ll begin exploring what it looks like to create a useful mock object in Swift. In particular, I’ll show common approaches to Swift mocks that can be greatly improved.
Do you have any questions so far? Or any reservations? Please share in the comments below!TDD Sample App: The Complete Collection …So Far]
Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He’s been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.