how-to-replace-type-methods-in-swift-to-improve-testability
Engineering
Oct 22, 2019

How to Replace Type Methods in Swift to Improve Testability

GetYourGuide
Careers Team
No items found.

Senior iOS Engineer, Otavio Cordeiro, focuses on making sure people love our product. He knows testing in Swift can be a challenge, especially when using type methods. But he's got a solution.

Like them or not, type methods, aka class methods or static methods, are heavily used in Swift and part of our daily lives as developers.

From analytics trackers to requesting system permissions, we’ve all had to face type methods from external libraries in which we had no control over. Testing code that interacts with them might seem hard without using method swizzling, but fortunately, this doesn't always have to be the case.

Let's suppose there's a class called APIWrapper, which implements a type method that requests search results from an endpoint.

‍

APIWrapper implemented as a type method
APIWrapper implemented as a type method

‍

The method takes a query, String, and a completion block, (Result<[String], Error>) -> Void, which is triggered once the request finishes. Its internal implementation doesn't really matter since it could be from an external framework or generated by a code generator from the API specification.

Consuming the method is straightforward:

‍

APIWrapper used in code
APIWrapper used in code

‍

But testing the part of the application that interacts with it isn't that obvious. Especially when we want to:

  1. avoid making real network requests
  2. stress all the possibilities the interface allows. For instance:
  3. Is the search method called with the correct query string?
  4. What happens when the completion block returns an empty array?
  5. What happens when the completion block returns an array of results?
  6. What happens when the completion block is called with an error?

Basic Data Source implementation

The snippet below shows SearchResultsTableViewDataSource, a UITableViewDataSource subclass which uses the API wrapper to populate the table view when the network request completes. Testing it triggers a real network request and doesn't provide what's needed to test the query passed to the wrapper and the different behaviors when the result returns.

‍

Data Source implemented using APIWrapper
Data Source implemented using APIWrapper

‍

If APIWrapper had an instance method instead of a type method, two easy and obvious solutions would emerge: protocols and subclasses. Using either/or it would be possible to replace the wrapper with a test double that captures the query parameter and completion block for verifying expectations.

Dependency injection to the rescue

In the example above, the API wrapper interface can't be changed and has to stay as a type method, therefore the easiest way to overcome this is via dependency injection. In this case, method injection via object initialization

The code below stores a method which matches the qualified symbol name of the wrapper method, using it to perform the search. In order to simplify the SearchResultsTableViewDataSource interface, the wrapper's search method is assigned using a default parameter value.

Data Source implemented using method injection
Data Source implemented using method injection

‍

Testing with Dependency Injection

Finally, testing becomes trivial. The injected method is used to capture the query term and the completion block passed to the API wrapper. Once captured, these can be used to verify expectations.

Data Source tests using method injection to replace the APIWrapper type method
Data Source tests using method injection to replace the APIWrapper type method

‍

More importantly, not only does this method work with type methods but also with free functions, such as SecItemAdd(_:_:) used to add items to the Keychain.

{{divider}}

Are you interested in joining the Engineering team at GetYourGuide? Take a look on our Tech Radar to check out the technologies we're using.

‍

Other articles from this series
No items found.

Featured roles

Marketing Executive
Berlin
Full-time / Permanent
Marketing Executive
Berlin
Full-time / Permanent
Marketing Executive
Berlin
Full-time / Permanent

Join the journey.

Our 800+ strong team is changing the way millions experience the world, and you can help.

Sign up to our newsletter

Oops! Something went wrong while submitting the form.