Most of our iOS application have to interact with at least one server or service through the network. The amount of apps consuming web services is only bound to grow.
When testing code that hits the network you expose yourself to all sort of problems, such as delays, timeouts, unexpected failures.
Why depending on the network is bad
One of the testing mantras is FIRST. Tests should be fast, isolated, replicable, self-verifying, timely. Reaching out to the network goes against some of these principles.
Slow
Having your tests hit the network adds the network speed as a constraint to how fast your tests can run, hence how quick your feedback cycle is.
Nondeterministic
Having your tests hit the network also adds a dependency on the data returned by the server. You can never be sure that the data returned is actually what you are expecting, unless you started a dedicated server only for the test purpose.
These two factors degrade the quality and reliability of your tests. Tests could time out because the network was particularly congested in that moment, or return unexpected data because the backend team run a migration on the staging server without notifying you.
Harder to test for error scenarios
Let me ask you a question: "how would you go about testing the behaviour of your networking components in failure scenarios?"
When interacting with a real server is not easy to get a failure. Maybe you can easily generate a 404, but what about a 500 or other errors? What about responses with unexpected data?
Testing our components behaviour when dealing with failures and unexpected input is as important, if not more, as testing the happy path. Depending on the network makes this task hard to accomplish.
Those are some of the reason why you should always decouple yourself from the network when writing unit tests. Now let's see how to do it.
How to decouple from the network in your unit tests
Think of the network as something your system uses, a dependency. As with other dependencies there are techniques to decouple from it. In the case of the network you have at least two options: using a stub library, or fake the network yourself.
Use a library
In the iOS and Mac development world you can use libraries like OHHTTPStubs or Nocilla to hijack network requests made by your app and prevent them from hit the real server by providing a stubbed response.
Head over to the next post for a deeper look into OHHTTPStubs.
Roll your own
Another option is to centralise all the network operations behind one single interface conforming to a protocol, for example:
protocol NetworkService {
func performRequest(
url: NSURL,
method: Method,
parameters: [String: AnyObject]?,
comptletion: ([String: AnyObject]?, NSError?)
)
}
All the components that need to interact with the network should be initialized
with an instance of an object conforming to the NetworkService
protocol to
use for their requests.
In your tests you can create a fake
network service conforming to NetworkService
and returning the response data
you want rather than hitting the network.
We'll look more into using a fake in an upcoming post.
I hope to have convinced you of the importance of decoupling yourself from the network in your tests, and intrigued you with the options you have to do so.
If you need help with your network stubs, or want to talk more about how to stub the network in your tests get in touch on Twitter or leave a comment below.
On other news: I started a podcast, is called TIL and I will shared daily(ish) tips on testing, automation, and general software development. I would really appreciate feedback on it, so please head over to https://www.briefs.fm/til a let me know what you think.