Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

How to Test Your Network Connection Requests in Swift Using URLProtocol

Matias GlessiFollowBetter Programming--ListenShareIn this article, we will see how to test network requests using the not-so-well-known Url Loading System, which intercepts requests made to the server. To address our problem, we’ll put some example code, assuming we have an implementation of this style in our productive code:Here, we see a simple example of an interface to an HTTP client called HTTPClient. This is made up of a single method that receives a URL and returns a result which can be a success (with its corresponding Data and Response) or a failure (with an Error).At the same time, it has a URLSessionHTTPClient implementation, which is responsible for communicating with the network. In our case, we use URLSession, Apple’s framework for network requests. As the focus is on understanding how to test this system component, we will leave an implementation already done. However, this could be created from the test decisions following Test-Driven Development (TDD).First, we will see some alternative strategies to test implementation, which, although valid, have some disadvantages to consider, which will guide this post.One way to resolve this could be to test the connection for real. The Request is made to the backend, the response is obtained, and it is evaluated if it is correct. Although it is a valid option, we easily find several reasons why this strategy can be problematic: What if the backend is not developed yet? How do we handle the multiple causes for which a connection can fail? How to increase the test duration if the connection is too slow? Although it is valid as a strategy, testing the component in isolation is probably better.Testing the service end-to-end way would be more useful if it took several components and how they are integrated.Since our implementation will use the Apple URLSession framework to make connections to the server, one strategy would be to mock it, implementing a subclass of it that can spy on or capture the information needed to validate our tests. For example, we could add flags to check that the methods were called, save certain values such as URLs sent or even the URLSessionDataTask used (mocking these, too), and validate they are correct.The problem with this strategy is that since it is a subclass of an Apple framework, there are many methods that we are not even aware of, which we should implement if we want to have full control of the class. Otherwise, our tests may end up using the parent class methods, which is dangerous since we would not be sure how it works (or if network requests are made in this specific case). In each release, even Apple can add new methods or update the old ones, changing how they work and causing our tests to fail.A third option to address the issue is to create protocols that mimic the interfaces we are interested in mocking. For example, we could create something like this:Thus, we would have a protocol very similar to URLSession (and another similar to URLSessionDataTask) that would only have the method that we are interested in mocking. In the test, our SUT will interact with the created protocol instead of the URLSession. This allows us to avoid assumptions about unknown methods and secret Apple implementations of how things work, in this case, regarding the URLSession API. It also saves us the need to update these tests in the future if Apple decides to update their methods since we only implement methods we care about.While this is another valid strategy, another problem is not the best solution: since we are mimicking the URLSession methods, there is a strong coupling with this API. Also, we’re adding productive code to satisfy our tests, which is definitely a wake-up call.According to Apple’s definition, the URL Loading System allows you to interact with URLs and communicate with servers using standard protocols (such as HTTP/HTTPS, for example) or with your own protocols you can create.How does it work? Every time a request is made, what happens behind the scenes is that there is a system (the URL Loading System) that processes it. As part of it, there is a type called URLProtocol, which is an abstract class that inherits from NSObject.So if we create our own URLProtocol and register it, we can start intercepting URL Requests. What is it for? In this case, we could evaluate the component using a particular protocol, implement some Cache, track information for Analytics, or even evaluate the performance of the requests.For this, we only have to implement the methods of the abstract class URLProtocol, which, although strange, is a class. In this case, we will create a mock that implements this clsass and evaluate the validity of the tested requests with the certainty that the requests are never made. No information is sent to any server, making the tests faster and more reliable.We will create our own since the URL Loading System processes requests through different protocols. This subclass of URLProtocol will have the objective of intercepting the transmitted information and validating it.Since we want to intercept the information from a URLRequest, we could store this information in a structure within our URLProtocolStub. A dictionary could be a good option:Thus, when preparing our test case in the part of its preparation, we can save this information and then perform the corresponding checks. Something like:Where the stub(url: data: response: error:) method of URLProtocolStub will have a form similar to this:Now that we understand how to store the information, we can create a test case. We will test the error case when the request has an error (error is not nil), and the client should return a .failure(error) result with the same error. It will be more or less like this:In the code above, we see our first test case. In the preparation part, we will create a URL, a specific Error, and an instance of our URLProtocolStub, where we will add the corresponding stub for that request with an error (and its other elements as nil). We will then create an instance of the client and make an asynchronous call (with expectation).Finally, we validate that the error received is the same as the one sent through an XCTAssertEqual(,), in the case of .failure(). In any other case, it is an unexpected result error.If you’re testing this in the IDE, it’s likely that nothing is compiling. This happens because we left the implementation of our Stub in the middle. We add that URLProtocolStub will be a subclass of URLProtocol, but we do not implement its requirements, which are mainly two. On the one hand, we must implement four methods of URLProtocol:On the other hand, we must register our protocol using the methods:Don’t worry, we’ll look at both requirements in detail below.Let’s review each of the methods we must override to meet the URLProtocol requirements. The first one will be canInit(with: URLRequest) -> Bool. If we return true in this method, we can process this request, and it will be our responsibility to complete the request with success or failure. We will be able to know if the urlRequest contains the necessary parts to do it.How can we know if we can process this request?Basically, as we are storing in a dictionary the information of that request (our Stub element) indexed through the corresponding URL, what will tell us whether it can process this request will be determined by whether we have the stub stored in the dictionary.But, if we add that method in XCode, we will have an error of the following type:This is because canInit is called a class method because we don’t have an instance yet. The URL Loading System will instantiate the URLProtocolStub only if the request can be processed. Since we don’t have an instance, we need to make some modifications.First, our stubs dictionary should be defined as static:So should the “stub” method:Finally, in our test case, we will have to change the instantiation of the Stub, which would be like this, without creating any instance:Moving on, the next method we need to implement is canonicalRequest(for request: URLRequest) -> URLRequest. This method returns a canonical version of the request, as described in the Apple documentation. It’s usually enough to return the same request since we shouldn’t make any changes to it, but maybe if you wanted to add a header or change the URL scheme (for example), it would be a good place to do it. In our case, it will be simply:Then we have to override the startLoading() and stopLoading() methods, which are instance methods. This means that they are executed once it is accepted, that it will process the request, and the necessary instance will be generated.We start with startLoading(). Here when this method is called, the URLProtocolStub implementation should start loading the request:Here, we get the request's URL (where the request is an instance variable) and our stub for that URL. With the guard, let us make sure we have them, and if not, we finish the execution. With the following if statement, if we get an error, we need to tell the URL Loading System that an error occurred, and we do this with another property of the URLProtocol instance, which is the client of type URLProtocolClient. This is an object that the protocol uses to communicate with the URL Loading System. This client has many methods; one tells the system that it failed with an error via urlProtocol(URLProtocol, didFailWithError: Error).We can check for the existence of data in our stub, and we can tell the client to load “data” through urlProtocol(URLProtocol, didLoad: Data).Similarly, we check for a “response”, which we will do through urlProtocol(URLProtocol, didReceive: URLResponse, cacheStoragePolicy: URLCache.StoragePolicy). In this case, we also send the Cache policy, which, since we did not deal with it in this article, we will send it as .notAllowed. Finally, once we finish, we must tell the client we finished the process with urlProtocolDidFinishLoading(URLProtocol).The last method we need to implement is stopLoading(), where the stop loading of a request is processed. For example, this could be used to handle a response to a cancellation. In this case, we won’t add an implementation so that it will look like this:It is important to implement it at least empty. Otherwise, we will have a crash at runtime. So, we complete the implementation of our stub. It should be like this:URLProtocol subclasses are not known to the URL Loading System just because they exist. We must register them before a request is made, and thus it will be visible to the system, searching for the different existing protocols and trying to process the request with each one of them.To do this, it is necessary to call the registerClass(AnyClass) class method that registers the protocol. Similarly, we can unsubscribe our URLProtocolStub with the unregisterClass(AnyClass) method. By adding these two lines at the start and end of the test case, we would make it clear to the URL Loading System that we want it to use our Stub. Our test case would look like this:If we run the test case, it should pass without problems. Yay!Although we could also add other test cases where we test other ways, as with .success(), it is a good starting point to get into the use of URLProtocol.By performing tests in this way, we can avoid assumptions about behaviors that we are mocking in the tests, that may be unpredictable in production, or even that may change in the future, modifying the nature of what we are evaluating. Also, we only add productive code if we need it for testing, which is usually not a good sign.----Better ProgrammingiOS Engineer @ NikeMatias Glessi--Sergei SavvovinBetter Programming--12Dmitry KruglovinBetter Programming--32Sami MaameriinBetter Programming--4Finsi EnnesinLevel Up Coding--1Atahan CeylaninYazılıma Dair--Bored Avo 🥑iniOS Interview Preparation Complete Guide--1Comviva MFS Engineering Tech Blog--Mobterest Studio--André Oriani--HelpStatusWritersBlogCareersPrivacyTermsAboutText to speechTeams



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

How to Test Your Network Connection Requests in Swift Using URLProtocol

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×