Build a Foursquare clone iOS app — Part 5: Network layer
- 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
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.
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
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_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
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
PlacesServiceshould 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
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
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
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
Then we just need to pass this method into
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
Now we can implement an structure that receives
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
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.
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.