Unit test Network Layer
To be honest.. writing unit tests in iOS is a myth. Usually iOS developers work for small startups, which source money from investors. It is important for those investors to create a product as soon as possible. Manuall tests or application stability? Who cares… It is our obligation as a professional developers we need to change this approach and teach our clients that development process consists of two inseparable elements -code & tests. If you want to learn more about this approach please check the book “The Software Craftsman: Professionalism, Pragmatism, Pride” (Robert C. Martin Series).
In this blogpost you will learn how to write unit tests of your network layer. After finishing you will see how simple it is. 👊
Before you start please fetch the latest changes from my repo because this tutorial is based on my previous post about Network Layer.
Contribute to marcinjackowski/NetworkLayer development by creating an account on GitHub.github.com
Quick and Nimble
You may wonder why did I choose Quick and Nimble instead of XCTest framework 🤔
The answer is simple - combination of those two libraries is simply, easy to use and you can define the expected behaviour in a more readable way. Quick is a testing framework while Nimble is a matching framework. If it’s your first time with both frameworks, check GitHub documentation where you can find many examples of use.
If you don’t understand these frameworks and don’t how to use them.. Don’t worry, just leave a comment and I will write a special blogpost about Quick and Nimble usage from the scratch 🍻
Last thing. In Quick framework there are four functions that describe tests: describe, context, it and fit. In my opinion these four don’t explain well when should they be used. To create tests that are more familiar and useful with general test representing style, create extension for Quick to transform this functions to Given, When, Then style. Check out the new syntax below:
- describe == given
- context == when
- it == then
- fit == onlyThen
Please create a new file
QuickCustomAliases and copy those lines:
As you can see, new functions better illustrate their usage and show the way how to construct tests.
ProviderProtocol has one function
request which takes two parameters:
service. If your provider is parsing response properly, at first you have to create service mock and then example model with a simple structure to test.
Create new file
ServiceMock and copy following code:
- This service has four cases to test the most common things:
jsonResponseWith200 -send plain request with json encoding. Expectations: status code 200.
jsonResponseWithURLParametersWith200- send request with parameters with json encoding. Expectations: status code 200.
errorWith500- Expectations: error with status code 500.
- Base url cannot be empty because string url must be valid. If it is empty — the app will crash.
- It is only a mock so you can write path you want.
One of the
ServiceMock cases return json data in response. It would be great to write tests of module responsibles for encoding json data. To do that, it’s necessary to create mock model which conforms to Codable and Equatable. Create new file
Before you start writing tests you have to modify current implementation of NetworkLayer just a bit. In version from previous blogpost, request function of URLSessionProtocol returns URLSessionDataTask 🤔 And that is a problem. In our test case you want to simulate resume task and return mock instead of real object. To do that create URLSessionDataTaskProtocol file and copy following code:
As you can see this protocol contains only one function
resume. In this particular case it’s enough, but in case when you want to cancel or pause task it’s easy to extend the protocol with new variants. Moreover URLSessionDataTaskProtocol must be an extension of URLSessionDataTask.
Next you need to change return value of request function from URLSessionProtocol to URLSessionDataTaskProtocol.
At the end you need to create mock which conforms to URLSessionDataTaskProtocol. You may wonder why? I will explain in in a later paragraph.
If you have already created ServiceMock and ServiceModel now it’s time to start testing! URLSessionProvider is the most important file of network layer. To write first test please create the file
URLSessionProvider with suffix
Spec. Every file which conforms to QuickSpec must contain this suffix.
If you look at the function request of URLSessionProvider you can see that in the second line of this function, session is calling dataTask to send request. So let’s create our first test:
💏 When: Request is called, 👶 Then: Session should call dataTask.
You may wonder how do we know if session has called function dataTask? SessionProtocol doesn’t have any properties called
dataTask, only function
dataTask 🤔 That’s why we love dependency inversion which is helpful in testing objects. Let’s create a new file URLSessionMock which conforms to URLSessionProtocol.
As you can see you have added new property
isDataTaskCalled with initial value false. If session call function dataTask you should change this property to true which means that this function was called. Furthermore to test URLSession you can’t return “real” URLSessionProtocol’s object so in init you should pass mock object and return this object in dataTask function. It’s time to complete first test and write first assertion.
CMD + U ⏲️ TADA! You wrote your first test!
In the same scope, it would be a good idea to write another test to check if url was set properly.
💏 When: Request is called, 👶 Then: Request should set proper url.
This test also checks our extension to URLRequest for building request from ServiceProtocol. Add lastURL property to URLSessionMock and assign url from request property.
New test will be easy to write.
CMD + U ⏲️ Test succeeded!
As you can see URLSessionProtocol returns URLSessionDataTaskProtocol.Can you guess what kind of test will be next? 🤔 Of course 🎊 Check if resume function was called!
💏 When: Request is called, 👶 Then: URLSessionTask should call resume function.
Error: Value of type ‘URLSessionDataTaskMock’ has no member “isResumeCalled” 🤔
Do you know what to do?
CMD + U ⏲️ Test succeeded!
💏 When: Request is called with json response, status code 200, 👶 Then: Request should complete with success.
Info: Before you start, please copy NetworkResponseMatchers file from my repo. This file is needed to facilitate compare enum cases.
This example is much more complicated than previous one but i will explain it step by step how to write a test. In this case we want to get response with status code 200 and parse json to get ModelMock object. First we need to modify URLSessionMock. Add new property:
This property will be set from URLSessionProviderSpec and will information about tested case (for example .jsonResponseWith200). Additionally add this following function inside dataTask:
This few lines switch over particular service and do some jobs.
dataTask function has completionHandler which returns (Data?, URLResponse?, Error?). In
.jsonResponseWith200 you need to do three things:
- Create ModelMock object with test value.
- Encode this object.
- Create response with status code 200.
- Call completion handler with just created parameters.
Let’s get back to URLSessionProviderSpec. What to do next? Create a new
when closure, then set service case to
As you can see there is no response property. Add this property under spec function with type
NetworkResponse<ModelMock>!. This property will keep information about response.
CMD + U ⏲️ Test succeeded!
💏 When: Request is called with json response and status code 200, 👶 Then: Should return given model
CMD + U ⏲️ Test succeeded!
It’s your turn now!
You just wrote a few tests. I hope you understand how to write unit tests for NetworkLayer and if you do — it’s time to practice. Please try to write some extra tests as your homework.
- 💏 When: Request is called when server is down (500), 👶 Then: Request should complete with failure response
- 💏 When: Request is called with bad request (400), 👶 Then: Request should complete with failure response
- 💏 When: Request is called with json response, URL parameters, status code 200, 👶 Then: HttpBody shoud be empty
- 💏 When: Request is called with json response, URL parameters, status code 200, 👶 Then: URL should contains parameters
- 💏 When: Request is called with json response, URL parameters, status code 200, 👶 Then: Request should complete with success response
- 💏 When: Request is called with json response, URL parameters, status code 200, 👶 Then: Request should return given model
If you are having some problems with those exercises check my repo. Or write me a private message on Twitter @mtc_jackowski.