The guide to unit testing in Swift with Apollo and GraphQL Part One
As iOS developers, we typically 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 and write some unit tests. 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 separate models for separate use cases.
This technique of only fetching what you need for a given screen can provide performance improvements for our application, decreasing our TTI (time to interaction) and improve the user experience.
The GraphQL Data Model
We will define a few GraphQL Fragments to design our model.
Next, let’s define our query to fetch a list of
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:
And finally, we add the Query to fetch a single
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
enum to express failure states within our networking layer.
Why are all of these objects marked as
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
Why do the data models conform to
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.
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
compactMap when it comes to transforming to deal with optionality.
The Swift Networking Service 📡
Let’s begin by defining a protocol for our
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
Failure states for our network calls. This simplifies error handling, and returns
Result<Value, Error> within an async completion block.
Now that we have our interfaces defined, lets create our actual service class.
Network Service Usage 🔧
First, we can add a factory to produce the
At he usage point where we would like to call our service we will create the factory and initialize the store.
result should contain the folowing data when you
po result in the LLDB.
In part two, we will implement unit tests to gain ~98% test coverage on our store object through mocking Apollo.
Originally published at gist.github.com.