How to test different permissions of CLLocationManager in test cases - swift

I am writing a test to test CoreLocation related function. This function will throw an error if the location services is not enabled.
func someFunction() throws {
guard CLLocationManager.locationServicesEnabled() throw NSError.init(
domain: kCLErrorDomain,
code: CLError.Code.denied.rawValue,
userInfo: nil)
}
...
}
In my tests, CLLocationManager.locationServicesEnabled() is always true. Is there a way to test the false scenario?

Instead of using static methods directly, wrap this call in a class and make that class adopt a protocol, so your code will depend on that interface instead of the concrete implementation.
protocol LocationManager {
var islocationServicesEnabled: Bool { get }
}
class CoreLocationManager: LocationManager {
var islocationServicesEnabled: Bool {
return CLLocationManager.locationServicesEnabled()
}
}
Then your function (or class) should receive that dependency instead of owning it (dependency injection):
func someFunction(locationManager: LocationManager) throws {
guard locationManager.islocationServicesEnabled else {
//...
}
}
In your tests you can pass a "Fake" LocationManager and test all the scenarios. (In your production code you pass the CoreLocationManager.)
That's a general advice for any dependency.

Related

NotificationCenter observer is “mutated after capture by sendable closure”

Consider this simple class:
import Foundation
class ExampleClass {
init() {
let notificationCenter = NotificationCenter.default
var observer: NSObjectProtocol? = nil
// A warning is emitted for the next line
observer = notificationCenter.addObserver(forName: .NSExtensionHostDidEnterBackground,
object: nil,
queue: nil) { [weak self] _ in
self?.doSomething()
notificationCenter.removeObserver(observer!)
}
}
func doSomething() {
print("We got the notification")
}
}
This code uses the exact pattern that Apple suggests in their documentation for NotificationCenter.addObserver(forName:object:queue:using:), where the NotificationCenter gives us some opaque token that conforms to NSObjectProtocol, and we later use that token to remove the observer.
Recently, though, this code has started to produce a warning. On the line where observer is assigned, the compiler complains that
'observer' mutated after capture by sendable closure
I understand where the compiler is coming from here; if observer is a value type, then the closure will indeed get an “old” version of it. In this case, though, what we get back from addObserver() does seem to be a reference type, because the code works fine. (It’s unfortunate that Apple doesn’t give us a more specific return type for that method.)
Does this warning indicate an actual problem in this case? If so, what’s the best alternative pattern to use?
You can store the observer on the object itself:
import Foundation
class ExampleClass {
private var observer: NSObjectProtocol?
init() {
let notificationCenter = NotificationCenter.default
observer = notificationCenter.addObserver(forName: .NSExtensionHostDidEnterBackground,
object: nil,
queue: nil) { [weak self] _ in
if let self {
self.doSomething()
notificationCenter.removeObserver(self.observer!)
}
}
}
func doSomething() {
print("We got the notification")
}
}
This workaround silences the warning, and it shouldn’t change the semantics of the program in a meaningful way. It’s a bit less elegant, in that a variable that could have stayed local to init (and its closures) is now exposed to the entire class, but it should have the same effect as the previous version of the code.

Mock AVCaptureDeviceInput for testing

I'm trying to test how my App responds to different AVFoundation configurations. I'm using the techniques described in the WWDC video 'Engineering for Testability.'
I created a protocol to represent the pieces of an AVCaptureDevice that my app uses.
public protocol AVCaptureDeviceProperties: class {
//MARK: Properties I use and need to test
var position: AVCaptureDevice.Position { get }
var focusMode: AVCaptureDevice.FocusMode { get set }
var exposureMode: AVCaptureDevice.ExposureMode { get set }
var whiteBalanceMode: AVCaptureDevice.WhiteBalanceMode { get set }
//MARK: Functions I use use and need to test
func lockForConfiguration() throws
func unlockForConfiguration()
func isFocusModeSupported(_ focusMode: AVCaptureDevice.FocusMode) -> Bool
func isExposureModeSupported(_ exposureMode: AVCaptureDevice.ExposureMode) -> Bool
func isWhiteBalanceModeSupported(_ whiteBalanceMode: AVCaptureDevice.WhiteBalanceMode) -> Bool
}
I have an extension that makes AVCaptureDevice conform to my protocol.
extension AVCaptureDevice: AVCaptureDeviceProperties {
//Don't need anything because AVCaptureDevice already has implementations of all the properties and functions I use.
}
I can now make an object for myself where I can configure all the properties for different test cases. Works great!
However, I need to take it another step further and get a mock AVCaptureDeviceInput object. This object only has one initializer that takes a AVCaptureDevice but I want to be able to mock initialize with my protocol type. So far I have this:
extension AVCaptureDeviceInput {
convenience init?(device: AVCaptureDeviceProperties) throws {
guard let downcast = device as? AVCaptureDevice else {
return nil
}
try self.init(device: downcast)
}
}
However, I will never get a successful initialization with a mock object that conforms to my protocol. How to I solve this problem so I can test?

How to UnitTest asynchronous calls to delegate methods in Swift?

I have the following class with a static method that uses MKDirections to calculate custom routes between two coordinates. Once it finishes calculating, the method uses a delegate to pass the route (an MKPolyline object) to the View Controller which adds it to a MapView as an overlay. Every route is assigned a title that determines in which color the route is rendered on the map.
class NavigationInterface {
weak static var routeDelegate: RouteDelegate!
static func addRouteFromTo(sourceCoor: CLLocationCoordinate2D, destinationCoor: CLLocationCoordinate2D, transportTypeString: String)
{
let sourcePlacemark = MKPlacemark(coordinate: sourceCoor)
let destinationPlacemark = MKPlacemark(coordinate: destinationCoor)
//var route = MKRoute()
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: sourcePlacemark)
request.destination = MKMapItem(placemark: destinationPlacemark)
request.requestsAlternateRoutes = false
//get MKDirectionsTransportType based on String identifier
request.transportType = getTransportType(transportTypeString: transportTypeString)
let directions = MKDirections(request: request)
directions.calculate { (response, error) in
if let directionResponse = response?.routes.first {
let route = directionResponse.polyline
route.title = transportTypeString
print("Got Here")
self.routeDelegate!.didAddRoute(route: route)
}
}
}
The delegate is defined through the following protocol:
protocol RouteDelegate: class {
func didAddRoute(route: MKPolyline)
func didAddBoundary(boundary: MKPolygon)
}
The View Controller implements the delegate as follows:
class MapViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
...
override func viewDidLoad() {
super.viewDidLoad()
NavigationInterface.routeDelegate = self
}
extension MapViewController: RouteDelegate {
// delegate Method
// called in Navigation Interface
func didAddRoute(route: MKPolyline) {
mapView.add(route)
}
func didAddBoundary(boundary: MKPolygon) {
mapView.add(boundary)
}
}
Now I have attempted to write a UnitTest which checks whether the delegate method "didAddRoute" returns the correct route
For this purpose I've created a test class "NavigationTests" which implements the RouteDelegate protocol and an test method that calculates a route and then evaluates the route returned from the "NavigationTests" protocol implementation of "didAddRoute":
class NavigationTests: XCTestCase, RouteDelegate {
var routes = [MKPolyline]()
var asyncExpectation: XCTestExpectation?
func didAddRoute(route: MKPolyline) {
routes.append(route)
asyncExpectation?.fulfill()
}
...
func testaddRouteFromTo(){
NavigationInterface.routeDelegate = self
asyncExpectation = expectation(description: "routes returned from delegate method")
NavigationInterface.addRouteFromTo(sourceCoor: CoordinateA, destinationCoor: CoordinateB, transportTypeString: "roadTravel")
let result = XCTWaiter.wait(for: [self.asyncExpectation!], timeout: 2.0)
if result == XCTWaiter.Result.completed {
let route = self.routes.first
XCTAssert(route!.title == "roadTravel", "failed to retrieve correct route")
print(route!.title)
} else {
XCTFail()
}
}
}
Now this test method randomly returns routes from the MapViewController implementation of RouteDelegate instead of the NavigationTests implementation. W
How can I avoid these unwanted references to the MapViewController and why is it created at all since I do not instantiate it in the test?
Ideally I would like to prevent the MapViewController from being instantiated when running this test class since it is not required for the Unit Test.
How can I make sure that only the NavigationTests implementation of the RouteDelegate is used?
Statics vs. tests
Because addRouteFromTo(sourceCoor:destinationCoor:transportTypeString:) is a static method, you have made NavigationInterface.routeDelegate static as well. When your tests run, they are setting a global variable. This means the tests have side effects which last beyond the scope of the tests.
Here are a couple of approaches to prevent this from happening:
a) Create a setUp() and tearDown(). In setUp(), save the old value of NavigationInterface.routeDelegate before overwriting it to self. In tearDown(), restore the old value.
b) Change from statics to an object. As a general rule, statics make things harder to test.
Prefer b). It is safer, and lets the pressure of testability improve your design.
…I don't see any references to MapViewController in your test. Was it created by your application delegate?
How to test an asynchronous call?
Now to your larger question. A test that does actual networking is slow and fragile. It depends on your network conditions. It depends on the back end. It introduces a time lag.
You would be better served by restructuring your code so that you can test the following:
Are you creating the correct MKDirectionsRequest?
Are you handling the response correctly?
This will be expressed in at least 2 tests, but probably more. Once you can independently test response handling, then you can test errors as well as successful responses.
So how do you test "create the response" independently from "handle the response"? By doing this work in separate methods. The tests can then just call these methods.
There is no need to test that Apple makes a network call, or does something on the back end, or sends a response. If you follow this approach, the need for asynchronous tests drops dramatically.
I hope this helps. If you need clarification, please ask. For more thoughts on how "the way Apple shows us to write code isn't good testable design," see https://qualitycoding.org/design-sense/

Setting function-scope variable to avoid using a capture list

I have the following class, which uses a closure in one of its methods:
class SomeClass {
let someOtherClassInstance: OtherClass
func performAsyncTask() {
DispatchQueue.global(qos: .background).async { [weak self] in
print("\(self?.someOtherClassInstance)")
}
}
}
I'm wondering if I can also rewrite performAsyncTask as:
func performAsyncTask() {
let instance = self.someOtherClassInstance
DispatchQueue.global(qos: .background).async {
print("\(instance)")
}
}
The main goal is that I can avoid making self weak in the capture list - or rather so that I don't have to access self at all. There seems to be no reference to self in the second version, but is there a possibility that there will be an error when I try to access instance?
That's fine (assuming that self.someOtherClassInstance has no
back references to the SomeClass instance). You can achieve the
same with a capture list:
func performAsyncTask() {
DispatchQueue.global(qos: .background).async {
[instance = self.someOtherClassInstance] in
print("\(instance)")
}
}
The closure captures a strong reference to the
OtherClass instance which is held until it has been executed,
but no reference to self.
Note that the closure accesses instance regardless of whether
the SomeClass instance still exists or not, so the behavior is
slightly different from what your first method does.

How do I reinitialized a property in a singleton class?

My problem that I'm facing right now is that whenever user loads up the app. The singleton object will run
Singleton design
import SocketIO
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
var socket: SocketIOClient!
override init() {
socket = SocketIOClient(socketURL: URL(string: mainURL)!, .connectParams(["token": getToken()])])
super.init()
}
func establishConnection() {
socket.connect()
}
func closeConnection() {
socket.disconnect()
}
func getToken() -> String {
if let token = keychain["token"] {
return token
}
return ""
}
}
Take a look at init() and the .connectParams, in order for the user to connect to the server, token must be present thus the getToken() being passed.
If the token is not there it will initialize the socket object without the token. I run the establishConnection at the applicationDidBecomeActive
func applicationDidBecomeActive(_ application: UIApplication) {
SocketIOManager.sharedInstance.establishConnection()
}
The token will only be there after the user logs in.
The main question is, is there any way to reinitialized the socket object? or do i use didSet or willSet method?
Maybe something like this?
var socket: SocketIOClient! {
didSet {
oldValue.closeConnection()
}
}
It looks like you could probably get rid of the ! too if you want, since you're setting it in your init, assuming SocketIOClient.init returns a non-optional instance.
It is simple, You just need to declare a method in your class:
func resetConnection() {
socket.disconnect()
socket = SocketIOClient(socketURL: URL(string: mainURL)!, .connectParams(["token": getToken()])])
socket.connect()
}
and use in the following
SocketIOManager.sharedInstance.resetConnection()
let socket =
SocketIOManager.sharedInstance.socket // this will be the newer
One way to to do that is to create a public method inside SocketIOManager, and use that method to initialize the socket:
func initializeSocket() {
socket = SocketIOClient(socketURL: URL(string: mainURL)!, .connectParams(["token": getToken()])])
}
And call this method after the user has logged in.
But the way, your initializer must be private in order to implement the Singleton design pattern properly.
Another note is that the initialization of static variables in Swift happens lazily, which means that they only get initialized the first time they are used. Check this answer and the Swift documentation on this topic for more information
First, you are calling this flow from AppDelegate, trouble with this is you depend on this token being present. So what jumps out at me here is that you're missing a method that checks if this token is actually present before initiating the connection, the method should just forgo connecting the socket entirely if you can't produce the token (that is, if your connection is actually token dependent, if it is not then previous answers should help you out).
Since you're right to initialize the socket within the init override of your manager class, it's going against what I think you want, which is to reset a connection once a token does become present if it was not there initially. For this, you should hold back on creating the socket as I mention above.
What I usually do for singletons: I give them a blank "Configure" method, to commit it to memory, usually on AppDelegate's didFinishLaunchin withOptions. If this method contains anything, it's those methods which check for any values the singleton is dependent on, and to assign a custom internal state to the singleton based on those values (like some enum cases). I would then call up establishConnection like you do here, but establishConnection should be a generic method which can run at every appDidEnterForeground method, but without having to worry about altering things, and it should re-establish things that were dropped while your app was backgrounded.
So i'd recommend altering your class to something along the lines of:
import SocketIO
enum SocketIOManagerState {
case invalidURL
case launched
case tokenNotPresent
case manuallyDisconnected
case backgroundedByOS
}
class SocketIOManager: NSObject {
private var state : SocketIOManagerState = SocketIOManagerState.launched
private var staticSocketURL : URL?
static let sharedInstance = SocketIOManager()
var socket: SocketIOClient?
override init() {
super.init()
}
func configure() {
//fetch the url string from wherever and apply it to staticSocketURL
guard let url = URL(string: "The URL from wherever") else {
state = SocketIOManagerState.invalidURL
return
}
if getToken() == nil {
state = .tokenNotPresent
} else {
//only here can we be sure the socket doesn't have any restrictions to connection
staticSocketURL = url
state = SocketIOManagerState.launched
}
}
func evaluateConnection() {
guard let token = getToken() else {
//maybe something went wrong, so make sure the state is updated
if socket != nil {
return evaluateSocketAsNotNil()
}
return closeConnection(true, .tokenNotPresent)
}
switch state {
case .tokenNotPresent, .invalidURL:
closeConnection(true)
break
case .launched:
//means token was present, so attempt a connection
guard socket == nil else {
evaluateSocketAsNotNil()
return
}
guard let url = staticSocketURL else {
//maybe something went wrong with the url? so make sure the state is updated.
if socket != nil {
return closeConnection(true, .invalidURL)
}
return setState(.invalidURL)
}
if socket == nil {
socket = SocketIOClient(socketURL: url, .connectParams(["token": token]))
}
socket?.connect()
default:
//unless you care about the other cases, i find they all fall back on the same logic : we already checked if the token is there, if we get here, it means it is, so should we reconnect?
guard weCanReconnect /*some param or method which you create to determine if you should*/ else {
//you determine you should not, so do nothing
return
}
//you determine you do, so:
}
}
private func evaluateSocketAsNotNil() {
guard let sock = socket else { return }
switch sock.state {
case .notConnected:
//evaluate if it should be connected
establishConnection()
case .disconnected:
evaluateSocketAsNotNil()
case .connecting:
//do nothing perhaps?
case connected:
guard getToken() != nil else {
//token is not present, but the socket is initialized, this can't happen so disconnect and reset the instance
closeConnection(true, .tokenNotPresent)
return
}
break //nothing to do here
}
}
private func establishConnection() {
guard let sock = socket else { return }
sock.connect()
}
func setState(_ to: SocketIOManagerState) {
self.state = to
}
func closeConnection(_ clearMemory: Bool) {
guard let sock = socket else { return }
sock.disconnect()
setState(.launched)
if clearMemory {
socket = nil
}
}
private func closeConnection(_ clearMemory: Bool,_ to: SocketIOManagerState) {
socket?.disconnect()
setState(to)
if clearMemory {
socket = nil
}
}
func getToken() -> String? {
guard let token = keychain["token"] else {
state = .tokenNotPresent
return nil }
return token
}
}
And your AppDelegate would then look like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
SocketIOManager.sharedInstance.configure()
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
SocketIOManager.sharedInstance.closeConnection(false, .backgroundedByOS)
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
SocketIOManager.sharedInstance.evaluateConnection()
}
From here, you can always call evaluateConnection() and closeConnection(_:, _:) anywhere else in the app, and add more state cases, and more ways to handle those cases logically. Either way, it's up to you to determine how you should connect and reconnect based on the token.
With this structure, if your user logs in, and you set your token properly in your app, you should then be able to connect the socket properly when calling evaluateConnection during the login process.
There's also alot of comments, and some things might seem generic (apologies), but it's up to you to fill in the blanks for your use-case.
Hope it helps!