Before I start the first networking calls in the iOS TDD sample app, I have a question for you.
How would you write unit tests for this code?
NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:completion]; [dataTask resume];
This fires off a request to download some data, then does something with that data. Most people would write the code pretty much this way. (Of course, supplying a URL and a completion block.)
Then they ask, “How do I unit test this?”
Think about it for a minute. What are your options?
It’s Dependencies All the Way Down
The third line depends on the second line, which creates dataTask. That in depends on the first line, which creates session. And that comes from a singleton, [NSURLSession sharedSession].
Oh, great. A singleton.
Is there a way to control it for unit tests? What are your options?
Method Swizzling to the Rescue?
At this point, an advanced Objective-C programmer might say, “We can swizzle it.”
Method swizzling is the act of reaching into a class during runtime. You replace one method with another. It’s magic, made possible by a dynamic language.
We can reach right into NSURLSession and replace sharedSession with a stub that we control. Then you don’t have to change the production code.
Method Swizzling with Helper Libraries
Instead of doing your own method swizzling, there are libraries that can help. In particular, there are libraries geared specifically for testing.
For Objective-C, a general-purpose approach is to use OCMockito. When you use OCMockito to stub a class method, it’s actually swizzling a replacement for the class method.
A more specialized approach is to use libraries that stub particular features. Stubbing-via-swizzling libraries include:
- OHHTTPStubs for networking
- Nocilla, also for networking
- TUDelorean for NSDate
- ViewControllerPresentationSpy for alerts, presenting, and dismissing view controllers
Stubbing via method swizzling means you don’t have to change production code. This makes it helpful for legacy code.
But even though I wrote OCMockito and ViewControllerPresentationSpy, I need to wave a caution flag…
What Method Swizzling Hides
Method swizzling for stubbing is a form of Dependency Injection called Ambient Context. (Note: There are other ways to do Ambient Context.) Like I said, it’s helpful for legacy code. But there’s a problem.
The problem with Ambient Context is that it hides the dependencies.
Dependency Injection isn’t just about “making something testable.” It’s a form of design, with its own feedback loop. When things feel awkward “because now I have to inject all these things (grumble),” we should pay attention to those feelings.
In TDD, we say, “Listen to the tests.” That’s a tighter version of a larger Object Oriented Design concept: “Watch out for code smells.”
So if you’re feeling awkward because a piece of code has too many dependencies… good. That feeling is telling you something important. Maybe it’s “Stop Making Big Classes.” Pay attention to the discomfort. Don’t sweep it under the rug.
There Are Always Other Options
I’ve spoken and written about Dependency Injection before. There are several ways to approach it. There are even different ways to do Ambient Context—especially on a class you own. Ambient Context can be appropriate for cross-cutting concerns, such as logging.
So the next time you’re staring at some code wondering, “How do I unit test this,” I want you to pause. Brain teaser puzzles have hidden breakthroughs.
Here’s the big “Aha!” idea for unit testing: Break free of the idea that you can’t change production code to make it testable.
Agree? Disagree? What other stubbing-via-swizzling libraries have you used? How has wrangling dependencies improved your code? Share your thoughts and experiences in the comments below.
Break free of the idea that you can't change production code to make it testable.
To me everything described here seems to be a little old… Are we not all by now using SWIFT?
Always in all examples there are little code snipplets explained. But has anyone been trying TDD on a really REAL BIG Project? Can anyone give an example on that?
I love TDD, I love the idea but reading something like this really makes me question if this is the future…
There is no “real big” project in existence that has managed complexity, reliability, and maintainability *without* unit tests.
The size of the project is not relevant to the effectiveness of TDD and unit tests. By their very definition, unit tests are very limited in scope and are equally effective in small projects and large projects.
People tend to make poor design choices when code bases get large and will start doing things that are hard to test and then claim that testing doesn’t work.
Doing TDD properly will help tremendously to avoid poor decisions because it literally forces you to write testable code.
Hope that helps!
Hi Timm, thanks for your questions.
Are we not all by now using SWIFT?
I’m currently working in a codebase containing 30,000 lines of Objective-C. So far, there is no compelling reason for us to rewrite any of it in Swift. Among the reasons not to, is that the tools are immature.
Always in all examples there are little code snipplets explained.
That’s the nature of trying to teach through blog posts. Eventually, my TDD sample app will be a larger example, but it’s still in its early stages. For a complete example with explanations, see Test-Driven iOS Development: The Book That Fills a Big Hole.
But has anyone been trying TDD on a really REAL BIG Project? Can anyone give an example on that?
I wouldn’t call this a BIG project, but it was a commercial app: iPhone Test Driven Development: Can It Really Work?
Most of the time, I work in legacy codebases. I start by learning how to bring the code I’m touching under test, to make sure I don’t break anything. Then it’s TDD from there.