Improving testability of CLLocationManager
Tags: agilewebandappde, agiletesting, codetesting, mobiledevelopment
Published: 2019-04-25 07:38:28

---
title: Improving testability of CLLocationManager
published: true
tags: Agile web and app de,agile testing,code testing,mobile development
canonical_url: https://apiumhub.com/tech-blog-barcelona/improving-testability-cllocationmanager/
---

It is common to find difficulties with the response of methods that are not ours when we write class tests that have external dependencies. Let’s see a typical case in [mobile development](https://apiumhub.com/mobile-app-development-barcelona/) where we can find this problem and find out how we can improve testability of CLLocationManager.  

 

## Improving testability of CLLocationManager

We have developed an app that has a map and uses the location services of Apple’s _CoreLocation_ to obtain the user’s location.

As we have said, the problem comes when our class uses the _CoreLocation framework_.  
 When calling the _requestLocation ()_ method, of the _CLLocationManager_ class, it tries to retrieve the user’s location and calls our delegated method that is in the class that the _CLLocationManagerDelegate_ delegate conforms to.

Our intention is to be able to control the values generated by methods such as requestLocation () and all this communication via delegates makes the process even more difficult.

Let’s see how we can do this by doing a mock of the external interface of the CLLocationManager using protocols.

We start with our class of location services that we will call _LocationService_.  
 It is a class that has the dependency with CLLocationManager and in turn also forms its protocol _(CLLocationManagerDelegate)_ for communication between them.

Basically the class has the _getCurrentLocation_ method that for now asks the framework for the user’s location and we show it in the delegate.

```

class LocationService: NSObject {
	
	let locationManager: CLLocationManager
	init(locationManager: CLLocationManager = CLLocationManager()) {
    	self.locationManager = locationManager
    	super.init()
    	self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
    	self.locationManager.delegate = self
	}
	func getCurrentLocation() {
    	self.locationManager.requestLocation()
    	self.locationManager.requestWhenInUseAuthorization()
	}
}

extension LocationService: CLLocationManagerDelegate {
	
	func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) {
    	guard let location = locations.first else { return }
    	print("The location is: \(location)")
	}
	func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    	print("Error: \(error)")
	}
}

```

Starting from our _LocationService_ class, the first thing we are going to do is change how our method of _getCurrentLocation_ returns as the location. And for this we are going to declare a callback that we will use within our method to return the location.

```

private var currentLocationCallback: ((CLLocation) -> Void)?

	func getCurrentLocation(completion: @escaping (CLLocation) -> Void) {
    	
    	currentLocationCallback = { (location) in
        	completion(location)
    	}
    	…
}

```

And in the delegate method we are going to call the new callback.

```

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    	guard let location = locations.first else { return }
    	self.currentLocationCallback?(location)
    	self.currentLocationCallback = nil
	}

```

As we have said before, our goal is to control the location values returned by CLLocationManager and we will write a test to verify it.

```

    func test_getCurrentLocation_returnsExpectedLocation() {
    	
    	let sut = LocationService()
    	let expectedLocation = CLLocation(latitude: 10.0, longitude: 10.0)
    	let completionExpectation = expectation(description: "completion expectation")
    	
    	sut.getCurrentLocation { (location) in
            completionExpectation.fulfill()
            XCTAssertEqual(location.coordinate.latitude,expectedLocation.coordinate.latitude)
            XCTAssertEqual(location.coordinate.longitude,expectedLocation.coordinate.longitude)
    	}
    	wait(for: [completionExpectation], timeout: 1)
    }
```

The test fails because logically it is obtaining the actual location of the device. We still don’t have a way to inject the value we want it to return.

To solve this we will start by creating an interface that will have the same methods as the _CLLocationManager_ class needs.

```

protocol LocationManagerInterface {
	var locationManagerDelegate: CLLocationManagerDelegate? { get set }
	var desiredAccuracy: CLLocationAccuracy { get set }
	func requestLocation()
	func requestWhenInUseAuthorization()
}
```

In our class we must change the references of _CLLocationManager_ to those of our new interface _LocationManagerInterface_.

```

    var locationManager: LocationManagerInterface
	
	init(locationManager: LocationManagerInterface = CLLocationManager()) {
    	self.locationManager = locationManager
    	super.init()
    	self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
    	self.locationManager.locationManagerDelegate = self
	}
```

The default argument of the constructor will not let us change it, it will warn us that the _CLLocationManager_ class does not conform the new interface that we have created.

We add an extension to the class CLLocationManager conforming our protocol to solve it.

```
extension CLLocationManager: LocationManagerInterface {}
```

We have a problem with our _LocationManagerInterface_ interface, and that happens because we are still coupled to the CoreLocation system delegate, _CLLocationManagerDelegate_.

In order to uncouple ourselves from this dependence, we will create our own delegate.

```

protocol LocationManagerDelegate: class {
	func locationManager(_ manager: LocationManagerInterface, didUpdateLocations locations: [CLLocation])
	func locationManager(_ manager: LocationManagerInterface, didChangeAuthorization status: CLAuthorizationStatus)
}

```

It will have two methods very similar to those already provided by the _CLLocationManager_ class.

I added the method of _didChangeAuthorization_ as an example to show that the location can be mocked, and we can do it with the other methods available as well.

Now we can change the _CLLocationManagerDelegate_ of our interface by the one we just created.   
 The compiler will warn us that we do not comply with the interface again.

So we assign delegate getters and setters to return our delegate but nevertheless use the CoreLocation system delegate to get the data.

```

extension CLLocationManager: LocationManagerInterface {
	var locationManagerDelegate: LocationManagerDelegate? {
    	get { return delegate as! LocationManagerDelegate? }
    	set { delegate = newValue as! CLLocationManagerDelegate? }
	}
}

```

Now that almost everything is connected, we must change the CLLocationManager protocol information to our protocol.

We will do this in two steps.

First we make our _LocationService_ class according to our new delegate.

```

extension LocationService: LocationManagerDelegate {
	func locationManager(_ manager: LocationManagerInterface, didUpdateLocations locations: [CLLocation]) {
    	guard let location = locations.first else { return }
    	self.currentLocationCallback?(location)
    	self.currentLocationCallback = nil
    	...
	}
```

And second, the thin one, that we already had _CoreLocation_, now will simply call the one we created.

```

extension LocationService: CLLocationManagerDelegate {
	
	func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    	self.locationManager(manager, didUpdateLocations: locations)
	}
	...
}
```

Going back to our test files, as we now have a class that we control, we can create a mock and overwrite the values we want to inject.

```

    struct LocationManagerMock: LocationManagerInterface {
    	var locationManagerDelegate: LocationManagerDelegate?
    	
    	var desiredAccuracy: CLLocationAccuracy = 0
    	
    	var locationToReturn:(()->CLLocation)?
    	func requestLocation() {
            guard let location = locationToReturn?() else { return }
            locationManagerDelegate?.locationManager(self, didUpdateLocations: [location])
        }
    }
```

We make the necessary changes to our initial test to finally pass the test.

```

    func test_getCurrentLocation_returnsExpectedLocation() {
    	// 1
    	var locationManagerMock = LocationManagerMock()
    	
    	// 2
        locationManagerMock.locationToReturn = {
        	return CLLocation(latitude: 10.0, longitude: 10.0)
    	}
    	// 3
    	let sut = LocationService(locationManager: locationManagerMock)
    	
    	let expectedLocation = CLLocation(latitude: 10.0, longitude: 10.0)
    	let completionExpectation = expectation(description: "completion expectation")
    	
    	sut.getCurrentLocation { (location) in
            completionExpectation.fulfill()
            XCTAssertEqual(location.coordinate.latitude,expectedLocation.coordinate.latitude)
            XCTAssertEqual(location.coordinate.longitude,expectedLocation.coordinate.longitude)
    	}
    	wait(for: [completionExpectation], timeout: 1)
    }

```

1: We create the instance of the new mock.

2: We pass the value that we want to return when the _requestLocation ()_ method is executed.

3: We inject our mock into the creation of the _LocationService_. Recall that in the init of the LocationService we invert the dependencies making the locationManager according to our interface and not a specific class, which in this case was _CLLocationManager_.

In the same way if we need to control the values of the authorization of localization permits we can do it in the same way, adding the signature of the method that we want to overwrite to our interface.

 

If you are interested in knowing more about CLLocationManager or mobile app development in general, I highly recommend you to subscribe to our monthly newsletter by clicking [here](http://eepurl.com/cC96MY). 

 

## If you found this article about CLLocationManager  interesting, you might like…

 

[iOS Objective-C app: sucessful case study](https://dev.to/apium_hub/protected-ios-objective-c-app-cornerjob-successfull-case-study-89e)

[iOS snapshot testing](https://dev.to/apium_hub/ios-snapshot-testing-2i79)

[How to simplify the data layer with MoyaRx and Codable](https://dev.to/apium_hub/how-to-simplify-the-data-layer-with-moyarx-and-codable-2eco) 

[Espresso Testing](https://dev.to/apium_hub/introduction-espresso-testing-pa9-temp-slug-6400625)

[Mobile app development trends of the year](https://dev.to/apium_hub/mobile-app-development-trends-of-the-year)

[Banco Falabella wearable case study ](https://apiumhub.com/tech-blog-barcelona/banco-falabella-wearable-ios-android/)

[Mobile development projects](https://apiumhub.com/software-projects-barcelona/) 

[Viper architecture advantages for iOS apps](https://apiumhub.com/tech-blog-barcelona/viper-architecture/) 

[Why Kotlin ? ](https://dev.to/apium_hub/why-kotlin-language-android-why-did-google-choose-kotlin--639)

[Software architecture meetups](https://dev.to/apium_hub/apiumhub-software-architecture-meetups-in-barcelona-31df)

 

 

The post [Improving testability of CLLocationManager](https://apiumhub.com/tech-blog-barcelona/improving-testability-cllocationmanager/) appeared first on [Apiumhub](https://apiumhub.com).