As I mentioned in my SOLID principles article, I have been trying to write testable readable code for about a year. I’m still working on it, but for now let’s admit I’ve written theoretical testable code (I know I have a long way to go, there is always).
I assume you accept that I am writing testable code and moving on.
Before getting into the subject of testing, there is one thing that must be known, and that is Dependency injection.
In fact, it is useful to know all of the Solid principles. I’m attaching the resources I recommend, it will help you understand the rest of the article.
https://betterprogramming.pub/swift-s-o-l-i-d-21203ba3a226
https://en.wikipedia.org/wiki/Dependency_injection
In order to test the network layer, we first need a network structure, for this, I will use the most basic structure that I use in my standard personal projects.
HttpClient simply has an initializer, this init expects an URLSession, and uses this urlSession in functions. Now we have a Client in the simplest way. Now we can move on to the subject that we need to focus on.
Thinking about the test…
We need to take a step back and think about what we’re working on. Basically, URLSession does all the work, we give a URL or URLRequest and accordingly, in the best scenario, it brings us real data. So when I want to test my Network requests, should we send a real request every time? Let’s not think of this as just one request, think of how many requests there are in an e-commerce application. Assuming that we have written the tests for each of these and run this test, we will be sending a lot of unnecessary requests to the server. This number of requests can reach incredible numbers. So what’s the solution?
Did you know we can customize URLSession? Don’t worry, I didn’t know either, that’s why we’re here :)
When the request is made via URLSession, urlSession first checks if there is any default protocol(URLProtocol), if there is, it continues from these sub-protocols. I think we got something, it looks like we need to write a custom sub-protocol and give it a urlSession. Let’s try!
We created a MockURLProtocol inherited from the URLProtocol. We can now use the sub-properties of URLProtocol.
- canInit — decides whether a urlProtocol is active or not.
- canonicalRequest — Returns the request itself, we can customize it before returning it.
- startLoading — This is where the request starts. where we will put our mock request in the next step.
- stopLoading — This is called if the request gets canceled or completed.
Now that we have learned the basics of URLProtocol, let’s make MockURLProcol usable for testing.
1 — First of all, we need a response and data to be included in the response. These should be able to come to us from outside and we should put the response from outside in the response of the request.
You can create a closure to provide these. the closure exactly corresponds to the requestHandler found in the code. Thus, we can pre-determine the response of the request that we have assigned to the MockURLProtocol class, and this request does not need to transform into a real request. Fantastic!
2 — We set true to canInit because we want this MockURLProtocol to be active.
3 —Finally, it came to using the response and data in the request handler.
First, we need to tell the client that we have a response, for this we call the client’s didReview function.
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
Now, that the client knows that we have a response, we need to put data in this response next. We already have this data.
client?.urlProtocol(self, didLoad: data)
Now that we have put the data, we are done and inform the client that the process is complete and we have successfully created a response to our request.
client?.urlProtocolDidFinishLoading(self)
If you are still with me, we have come to the most beautiful part of the real work, we are writing a testtt 🎉.
At this stage, I don’t plan to talk about the basics of the Test, so I assume you have the Basic Testing knowledge.
We worked so hard to create MockURLProtocol and now it’s time to use it.
We create the URLSessionConfiguration inside the setUp function and assign the MockURLProtocol to the protocolClasses object. So this urlSession now has our mock structure and we can send fake requests.
Finally, we create our test function, in this case we will test the successful data.
First of all, we create our successful response. This is a classic HTTPURLResponse, since we are testing a success scenario, we assign 200 to the statusCode.
Then we assign our response and mock data to the request handler closure in MockURLProtocol. Thus, we know the response and data we will receive when we make a request.
So now everything is ready for us to send our request. We are sending a request from a ViewModel as if we were sending it. The only difference is that we compare the result here with our mock data.
As a result, the final state of the test function will be like this:
And now we can run our test. As you can see the test really succeeded 🥳.
FailScenario Test Case
Async - Await
We can convert the entire project to async, all we need to do is replace the closures with async and await
Alamofire
All we need to do is use Session Manager instead of URLSession, so we can throw Mock requests in Alamofire.
Conclusion
As a result, we can obtain a test-open environment with the mock urlSession we created. Now the rest is up to our imagination.
Isn’t it a wonderful feeling? I love programming 💛.
If you have any suggestions, questions or errors that you see, please do not hesitate.
You can find the whole project on github.
Let’s keep working….