Build a Foursquare clone iOS app — Part 5: Network layer
Content
- Part 1: Introduction and setup
- Part 2: Location data and managing dependencies
- Part 3: Continuous Integration
- Part 4: Streaming location
- Part 5: Network layer
- Part 6: State management
Introduction
When I was elaborating the scope for this app, I didn’t want the main data source to be a simple API call, but something a bit more complex, that depends on an integration of two data sources. This way the test would be more complex and it would require more planning and study.
In this case the final data displayed to the user depends on user location provided by the iOS SDK that will be used to request the Foursquare API for the nearby places.
iOS Networking
Now that we have a reliable way to obtain the user location, we can use this information in the Foursquare Places API request to fetch the nearby places. Moya dependency will help us in this task, specially by automatically wrapping the HTTP response into an Observable
.
The endpoint we are interested in is the “Get Venue Recommendations”. By reading the docs, the request parameters we need to send are:
v
: the version of the API we want to use. I’m using the current date this article is being written.venuePhotos
: boolean to include photos in the responselimit
: number of resultsll
: latitude and longitudeclient_id
andclient_secrent
: the credentials you obtain by registering in the Foursquare developers platform
To translate all this into a language that Moya understands we need to create an enum
which each case
will represent an endpoint for a given url. Then, this enum
needs to conforms to the TargetType
protocol from Moya
.
I’ve also created a struct
called “LocationPlaces” and all other models as well to map the json response. Since there’s no big secret in this task, I will hide the implementation details of this part. You can check the Github repository if you want to see the final implementation of the models.
To perform the HTTP request using the configuration file above, map into the LocationPlaces
enum and wrap it into an Observable
you just need to:
The piece of code above should print the nearby places of the latitude/longitude provided.
Looking good so far, but can I test this?
We already have something that retrieves the user location and something else that retrieves the nearby places giving a location, so we could just wrap it everything together, and find a way to show this data into an UIViewController
. But we are committed to write a testable code with a nice architecture, so we are going the hard way.
Luckily, Moya already provide us a good support for unit tests, by using the sampleData
method from TargetType
protocol. This method allow the stubbing of the HTTP response.
The two tests cases I’ve elaborated so far are:
- When the API returns a valid JSON, the app should be able to map it into a valid model.
- When the API returns an error, the wrapper class
PlacesService
should send this same error forward to be handled at UI. In other words, this shouldn’t throw a fatal error (like a crash).
Stubbing the HTTP Reponse
First of all, we need to create a Data
representing the JSON response of the Foursquare API. One way to do this is by creating it programmatically in the code, but since the response is long, we will create from a text file.
To obtain the response we can perform the request in a browser (like Firefox) by accessing directly the URL (don’t forget to fill your own client id and secret):
You should see something like my screen below. Tap the save button and save the response into a file (like venues.json
).
Then add this file to the XCode project. I’ve decided to add into folder called “Stubbed Responses”, which you can check the Github repository.
Now you can instruct PlacesApi
to use the content of this file to create the return of the sampleData
method:
Instead of performing the real network request, you can instruct MoyaProvider
to use the stubbed response, simply by passing this behavior in the constructor:
let provider = MoyaProvider<PlacesAPI>(stubClosure: MoyaProvider.immediatelyStub)
The stubbed response allow us to implement the first test case mentioned above, which ensures the application will create a valid model instance for the expected JSON response.
Now we need a data structure which we allow us to switch between the stubbed response (for tests) and the real implementation. The wrapper adapter pattern, as described in the official documentation of Moya will be perfect for this. Our wrapper structure will be called PlacesService
:
The implementation of the first test case is now pretty straight forward;
Don't forget to test the error case
Moya also provides an endpoint closure support which we can use to simulate a network failure. First, we declare this closure in the PlacesApi
file:
Then we just need to pass this method into MoyaProvider
constructor:
Now we can glue everything in a test case:
After a little work we now have a class that fetches nearby places using the Foursquare API, and another class that can obtain the user location. The next step is to get the data from the latter and fetch the places nearby the user position.
Finally, we can have the final data
I’ve decided to use a new structure to receive both data sources classes and then return the final result needed to be displayed on UI.
This also allow us to spy on the PlacesService
to ensure the implementation is done correctly.
To spy properly, we need this structure to depend on an interface instead of the real implementation of the network request class.
The protocol will be called PlacesDatasource
and the implementation PlacesService
:
Now we can implement an structure that receives PlacesDatasource
and UserLocationDatasource
as dependencies and performs the communication between them:
Note the use of the flatMap
operator, which we use to transform an Observable<CLLocation>
into an Observable<LocationPlaces>
.
The test we need to do now is to ensure the location returned by UserLocationDatasource
is properly given to PlacesDatasource
. To achieve this, we will use a test technique called spy, which consists on creating an implementation of the PlacesDatasource
protocol with public
variables, which can be used to check if a method was called correctly.
Note we are also not interested in the return value of this method, so we just return anything accepted by the compiler.
I've also created a mock for UserLocationDatasource
, then we can easily obtain an location:
Now we have everything we can write the test case:
As planned, in this test we instantiate NearbyPlacesServices
with its two dependencies, an UserLocationService
mock which always returns the same location, and a PlacesDatasource
mock which allow us to spy on the received latitude and longitude sent by the previous. Then, the assertion just ensures the latitude/longitude have the same values.
Conclusion
We could learn how to gather data to display on our app that comes from two different sources, webservice and native api, which are also dependent. The chosen object-oriented architecture gives the possibility to write tests and make the code more robust.