Build a Foursquare clone iOS app — Part 2: Location data and managing dependencies

Content

Location data source

As mentioned in the first part, the app will rely on user location to display its nearby interesting places. Unfortunately, the iOS native location API isn’t simple as I would want, so I’ve decided to wrap this logic into another class called UserLocationService.

Dependency management

Since the main goal is to achieve high testability with our code, the dependency of the wrapper class (the CLLocationManager provided by iOS SDK) won’t be instantiated by the wrapper class itself, but passed externally by whoever needs to use the wrapper. Personally I like to use the init (constructor) method for this.

This way you also make it explicit to another developer which are the dependencies of this class.

UserLocationService wrapper has to inherit NSObject to conform with NSObjectProtocol and also conform with CLLocationManagerDelegate protocol. Otherwise we could use a struct instead of a class, and enjoy the benefits of immutability.

The constructor ‘documents’ the class dependencies

But even by using dependency injection, we still can’t easily substitute the real dependency by a fake/mock when testing. Ideally the dependency would need to be an interface in an objected-oriented language, and we can actually achieve this by using the Protocol-Oriented Programming concept.

Procotol-Oriented Programming (POO) to the rescue

We will use this Swift feature to tell the compiler that we want the native CLLocationManager class to conform to a protocol declared at our project:

The compiler now believes a native class conforms to our new declared protocol

This allow us to make our wrapper depends on an abstraction (protocol) instead of an implementation (dependency inversion principle), and easily mock the CLLocationManager behavior.

The UserLocationService receiving an interface as a dependency (instead of receiving the implementation CLLocationManager) will look like this:

Added the necessary methods of CLLocationManagerDelegate

As you can notice, this class will be responsible for receiving the location updates and will provide this information for whoever needs. When testing we can pass a mock implementation in the constructor method, and the real CLLocationManager otherwise.

Dependency injection responsibility

Until now, the UserLocationService class has been removed from the responsibility of instantiating its own dependency, but that role still needs to be done to whoever instantiates the wrapper class.

A dependency injection framework (Swinject in this case) can manage this responsibility and also switch between the real and mock implementations accordingly. Let’s see how this works in the first test case, but before we need an initial mock implementation of LocationManager protocol to use in the tests:

Will do nothing when ‘requestLocation’ is called

Configuring the container

A container in the Swinject library is a class that we can use to register all dependencies of our application and how we want it to be instantiated. We will use two types of containers in this app, one for the test environment (which will contain the mocks) and one for the common application (containing the real implementations).

Now, we will start writing the tests for the new UserLocationService class, starting by creating a test container and registering its dependencies:

A new container is created before every test

In lines 11–12 we are instructing the container to use an instance of LocationManagerMock when someone asks for a LocationManager type. We had to configure the container object scope so we can have access this instance later in the tests.

In lines 13–14 when resolving the UserLocationService dependency, we are telling the container to use the previously registeredLocationManager.

I’ve decided to exceptionally use the force unwrap here because I’ve also written separate tests for the dependency resolution, ensuring all the dependencies are registered correctly.

First test cases

When elaborating test cases, I’d like to use a similar technique described in the book Working Effectivelly with Legacy Code by Michael Feathers, called “Telling the Story of the System”. In the book, this technique is used to communicate a general overview of a software. So by using this techinique, when writing test cases I like to ask myself: “What would I want UserLocationService to do?” and then answer in a storytelling format:

  • I’d like the delegate of LocationManager to be an UserLocationService instance.
  • I’d like UserLocationService to ask for user location permission when asked for coordinates.

Given our classes has been already designed to be tested, we only need to convert the test cases above into code:

Conclusion

These initial test cases already guarantees a working foundation for the app. It may seem obvious cases and not adding real value, and it’s probably because we didn’t follow the TDD baby steps (red-green-refactor) which would made the test cases much more intuitive.

Now that we have some working code we can setup a Continuous Integration environment to keep validating the current tests and all future commits.

Part 3: Continuous Integration

--

--

--

Stories and technical tips about building apps for iOS, Apple Watch, and iPad/iPhone

Recommended from Medium

Measuring and Evaluating Service Level Objectives (SLOs)

SQL challenge

How to animate Alert Dialog Position in Flutter

The Cost of Making a Professional Mobile App

Agile for Productivity — Part 2: Minimize interruptions, maximize flow

Agile for Productivity — Part 2: Minimize interruptions, maximize flow

Create package Python

Sitecore WFFM Unofficial Hotfix: Form Data Fails to be Aggregated

These are some shortcut keys in SAS. Have a look..

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Fabio Hiroki

Fabio Hiroki

fabiothiroki.github.io

More from Medium

iOS App Security — keeping apples worm-free

Swift Essentials| A Complete Guide to Swift Operations

How to Create Your Own iOS Password Generator

Is Swift the Objective Choice now?