I’ve shown you how to replace real objects with fakes in Swift, so that you can begin creating useful mock objects. We did this by moving away from a concrete type to a protocol. Anything that implements the protocol will work, so fakes are no problem.
But what do we do when we can’t change the signature? How do we substitute fake arguments, or a fake return value?
It’s time to talk about partial mocks in Swift.
In our TDD sample app, we’re making a Service object that calls the Marvel Comics API. We’ve TDD’d constructing the NSURLSessionDataTask from the request model. And we generate the authentication parameters by injecting a closure.
The next thing is to get the data task to start, by sending it a
resume message. Here are the steps in the test:
resumewas sent to the mock data task.
In Objective-C where mocking is easy, it looks like this using OCMockito:
The mockDataTask isn’t actually an NSURLSessionDataTask. But Objective-C doesn’t mind, as long as it responds to the same messages.
But Swift won’t let us pass an arbitrary fake object. The return type of the URLSession method is a URLSessionDataTask. If we owned the method, we could change the return type to a protocol. But Swift insists on it being a URLSessionDataTask.
Thankfully, URLSessionDataTask is declared as an
open class. This means we can subclass it. Then we’ll override the method we care about:
We also need to modify our MockURLSession. Let’s parameterize the return value by turning it into a property:
With these tools in place, we can create the test:
The code to pass this test actually blows up the other tests, because it now calls the force-unwrapped dataTaskReturnValue. Let’s fix this by moving the creation and insertion of the mockDataTask into the common test fixture. Swift has led us to do the correct thing here, because we don’t want any actual calls to
resume in our unit tests.
Our test is now simply:
MockURLSessionDataTask is a “partial mock” because we’re taking a real class and replacing one method.
Under other circumstances, I’d consider this to be a code smell. Here’s the problem: A partial mock mixes test code with production code in a single object.
But Swift gives us no choice. “Subclass and override method” is a classic technique for working around legacy code. It pains me to use this method on new code, but we must have tests.
Here are my evolving rules around mocks in Swift:
What if you can’t subclass the type to create a partial mock? You may need to call that section of code “un-unit testable”. Shed a tear, and retreat to the next available test seam. You may be able to find other ways of automating tests for that section of code. Probably not unit tests, though.
Let’s hope we don’t have to retreat too often!
Do you have any questions, concerns or remarks? Please leave a comment 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.