Skip to content

Instantly share code, notes, and snippets.

@thexande

thexande/blog.md Secret

Created December 16, 2018 04:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thexande/46e87acd86a6dcffa7000356070c9c7f to your computer and use it in GitHub Desktop.
Save thexande/46e87acd86a6dcffa7000356070c9c7f to your computer and use it in GitHub Desktop.

The guide to unit testing in Swift with Apollo and GraphQL

In this article, I will be discussing various techniques I have used to create high unit test coverage services in Swift utilizing GraphQL and the Apollo Library. GraphQL provides us with a powerful interface to request only the data we need, however this additionall layer can prove difficult to unit test.

As iOS developers, we typially rely on mocking URLSession in order to write unit tests for our network layer. Given that GraphQL and Apollo abstract our networking layer and act as a sort of black box for networking resources, we cannot rely on the typical URLSession mock strategy to achieve maximum unit testing coverage.

Let's use a public GraphQL API to create an example application. Our example app will be a simple table view master detail application which lists the countries of the wold and can load details about them on the detail page.

Initially, let's think about how we're going to structure our data. Consider that the master page only needs a few data points on a country, where the details page will require far more data about a specific country. This is one of the reasons to use GraphQL, we can actually define seperate models for separate use cases. This can provide preformance improvements for our application, decreasing our TTI (time to interaction) and improve the user experience.

The GraphQL Data Model

https://gist.github.com/2079335ad57fcd93534a6a36a1885ce7

Next, let's define our query to fetch a list of CountryLite models:

https://gist.github.com/223ef85136816f5b8594ec1022319b01

Now, we can consider our detail page. On this page, we will require more data about a given country than on the master table view. We can create that Fragment as follows:

https://gist.github.com/cd19fda4eea925279e7d95578880a251

And finally, we add the Query to fetch a single CountryDetail model.

https://gist.github.com/67b8041eaa43afb40f0465d42c4ae032

Excellent. Now that we have our data models defined, let's get into the actual networking service class implementation in Swift.


The Swift Data Model

Let's begin by defining a namespace for our domain models and Store, which will act as our service class in our case. Initially, we will define structs to express our GraphQL data returned from Apollo with the queries above. This will ensure that we aren't allowing GraphQL to "leak" into our application any further than we need. Additionally, we will define an Error conforming enum to express failure states within our networking layer.

Why are all of these objects marked as public?

In my implementation, the data layer exists in a Cocoa Touch Framework, not the main application target. This is also why inits are required here, you cannot automatically synthesize a publicly accessible initializer with Swift struct.

Why do the data models conform to Hashable?

Considering we require Equatible conformance for testing equality within our unit tests, conformance to Hashable will acheve this, as well as providing a hashValue property should we require it for caching.

https://gist.github.com/2826023dd61f131bc4cfb72b2eb47874


Dealing with Optionality within GraphQL data 🧐

After defining our data model in Swift structs, we should create optional initalizers for these objects from the GraphQL models. This will make actually parsing our data and transforming it into our domain models within our service simple. Apollo makes everything optional 😡, so all of our initializers are failable. You might know a better way to remove optionality from GraphQL data, please post a comment if you do.

We will rely on flatMap and compactMap when it comes to transforming to deal with optionality.

https://gist.github.com/cfdae5c566eb53c98705a4c7ab24fa45


The Swift Networking Service

Let's begin by defining a protocol for our Store's interface.

https://gist.github.com/176c72b87d2dc0e1106737812032157b

Next, let's define a protocol describing only the functionality from Apollo which we need, called ApolloClientInterface. For now, this is only Queries, as we are not preforming any Mutations. From there, let's ensure the ApolloClient conforms to our protocol.

We will use the Result library to handle Success and Failure states for our network calls. This simplifies error handling, and returns Result<Value, Error> within an async completion block.

https://gist.github.com/5362f17c8de1fc0372732a6ee8664ee6

Now that we have our interfaces defined, lets create our actual service class.

https://gist.github.com/de01f9a4646c0761b77a27efe0792479


Network Service Usage 📡

First, we can add a factory to produce the World.Store objects.

https://gist.github.com/d656d271d508dbfd6b9bb863222e350c

At he usage point where we would like to call our service we will create the factory and initialize the store.

https://gist.github.com/c19a0404e6e8f8393383fab4effbf7a3

The result should contain the folowing data when you po result in the LLDB.

https://gist.github.com/ea81b8b67e8bd53031e6c14879d07585

With these CountryLite objects, you could render the list of countries into a UITableView. We will look at integrating the data into an actual Swift iOS application in Part 2.

@as14
Copy link

as14 commented Dec 18, 2018

Good work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment