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

Fabio Hiroki
iOS App Development
4 min readApr 16, 2018

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

--

--