Build a Foursquare clone iOS app — Part 5: Network layer

Content

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 response
  • limit : number of results
  • ll : latitude and longitude
  • client_id and client_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:

Everything seems to be working as expected

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:

  1. When the API returns a valid JSON, the app should be able to map it into a valid model.
  2. 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):

https://api.foursquare.com/v2/venues/explore?v=20171029&venuePhotos=1&ll=-23.5666151,-46.6463977&limit=10&client_id=CLIENT_ID&client_secret=CLIENT_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:

The same error is expected to be passed

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 :

Updated PlacesService with the new protocol

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.

Latitude and longitude are the spied values

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.