![Page 1: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/1.jpg)
#WWDC18
© 2018 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.
Brian Croom, XCTest Engineer Stuart Montgomery, XCTest Engineer
•Testing Tips & Tricks • Session 417
![Page 2: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/2.jpg)
![Page 3: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/3.jpg)
![Page 4: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/4.jpg)
•Testing network requests •Working with notifications •Mocking with protocols •Test execution speed
![Page 5: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/5.jpg)
•Testing network requests •Working with notifications •Mocking with protocols •Test execution speed
![Page 6: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/6.jpg)
Pyramid of Tests Recap
•End-to-end
•Unit
•Integration
Engineering for Testability WWDC 2017
![Page 7: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/7.jpg)
Pyramid of Tests
•Unit
![Page 8: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/8.jpg)
Pyramid of Tests
•Integration
![Page 9: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/9.jpg)
Pyramid of Tests•End-to-end
![Page 10: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/10.jpg)
Pyramid of Tests•End-to-end
•Unit
•Integration
![Page 11: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/11.jpg)
Networking Stack
App Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
Server
App
![Page 12: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/12.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 13: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/13.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 14: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/14.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 15: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/15.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 16: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/16.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 17: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/17.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 18: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/18.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 19: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/19.jpg)
func loadData(near coord: CLLocationCoordinate2D) { let url = URL(string: "/locations?lat=\(coord.latitude)&long=\(coord.longitude)")! URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { self.handleError(error); return } do { let values = try JSONDecoder().decode([PointOfInterest].self, from: data)
DispatchQueue.main.async { self.tableValues = values self.tableView.reloadData() } } catch { self.handleError(error) } }.resume()}
![Page 20: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/20.jpg)
Unit Tests
App Update ViewCreate URLSession Task
Server
Parse ResponsePrepare URLRequest Create URLSession Task
App
Update ViewCreate URLSession Task
![Page 21: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/21.jpg)
Unit Tests
App Update ViewCreate URLSession Task
Server
Parse ResponsePrepare URLRequest Create URLSession Task
App
Parse ResponsePrepare URLRequest Update ViewCreate URLSession Task
![Page 22: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/22.jpg)
Unit Tests
App Update ViewCreate URLSession Task
Server
Parse ResponsePrepare URLRequest Create URLSession Task
App
Parse ResponsePrepare URLRequest
![Page 23: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/23.jpg)
struct PointsOfInterestRequest {
func makeRequest(from coordinate: CLLocationCoordinate2D) throws -> URLRequest {
guard CLLocationCoordinate2DIsValid(coordinate) else {
throw RequestError.invalidCoordinate
}
var components = URLComponents(string: "https://example.com/locations")!
components.queryItems = [ URLQueryItem(name: "lat", value: "\(coordinate.latitude)"),
URLQueryItem(name: "long", value: "\(coordinate.longitude)") ]
return URLRequest(url: components.url!)
} func parseResponse(data: Data) throws -> [PointOfInterest] {
return try JSONDecoder().decode([PointOfInterest].self, from: data)
}
}
![Page 24: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/24.jpg)
struct PointsOfInterestRequest {
func makeRequest(from coordinate: CLLocationCoordinate2D) throws -> URLRequest {
guard CLLocationCoordinate2DIsValid(coordinate) else {
throw RequestError.invalidCoordinate
}
var components = URLComponents(string: "https://example.com/locations")!
components.queryItems = [ URLQueryItem(name: "lat", value: "\(coordinate.latitude)"),
URLQueryItem(name: "long", value: "\(coordinate.longitude)") ]
return URLRequest(url: components.url!)
} func parseResponse(data: Data) throws -> [PointOfInterest] {
return try JSONDecoder().decode([PointOfInterest].self, from: data)
}
}
![Page 25: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/25.jpg)
class PointOfInterestRequestTests: XCTestCase { let request = PointsOfInterestRequest()
func testMakingURLRequest() throws { let coordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893)
let urlRequest = try request.makeRequest(from: coordinate)
XCTAssertEqual(urlRequest.url?.scheme, "https") XCTAssertEqual(urlRequest.url?.host, "example.com") XCTAssertEqual(urlRequest.url?.query, "lat=37.3293&long=-121.8893") } }
![Page 26: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/26.jpg)
class PointOfInterestRequestTests: XCTestCase { let request = PointsOfInterestRequest()
func testMakingURLRequest() throws { let coordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893)
let urlRequest = try request.makeRequest(from: coordinate)
XCTAssertEqual(urlRequest.url?.scheme, "https") XCTAssertEqual(urlRequest.url?.host, "example.com") XCTAssertEqual(urlRequest.url?.query, "lat=37.3293&long=-121.8893") } }
![Page 27: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/27.jpg)
class PointOfInterestRequestTests: XCTestCase { let request = PointsOfInterestRequest()
func testParsingResponse() throws { let jsonData = "[{\"name\":\"My Location\"}]".data(using: .utf8)! let response = try request.parseResponse(data: jsonData) XCTAssertEqual(response, [PointOfInterest(name: "My Location")]) } }
![Page 28: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/28.jpg)
class PointOfInterestRequestTests: XCTestCase { let request = PointsOfInterestRequest()
func testParsingResponse() throws { let jsonData = "[{\"name\":\"My Location\"}]".data(using: .utf8)! let response = try request.parseResponse(data: jsonData) XCTAssertEqual(response, [PointOfInterest(name: "My Location")]) } }
![Page 29: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/29.jpg)
Unit Tests
App Update ViewCreate URLSession Task
Server
Parse ResponsePrepare URLRequest
App
Parse ResponsePrepare URLRequest
![Page 30: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/30.jpg)
Unit Tests
App Update ViewCreate URLSession Task
Server
Parse ResponsePrepare URLRequest
App
Create URLSession Task
![Page 31: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/31.jpg)
protocol APIRequest { associatedtype RequestDataType associatedtype ResponseDataType
func makeRequest(from data: RequestDataType) throws -> URLRequest func parseResponse(data: Data) throws -> ResponseDataType}
class APIRequestLoader<T: APIRequest> { let apiRequest: T let urlSession: URLSession
init(apiRequest: T, urlSession: URLSession = .shared) { self.apiRequest = apiRequest self.urlSession = urlSession }}
![Page 32: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/32.jpg)
protocol APIRequest { associatedtype RequestDataType associatedtype ResponseDataType
func makeRequest(from data: RequestDataType) throws -> URLRequest func parseResponse(data: Data) throws -> ResponseDataType}
class APIRequestLoader<T: APIRequest> { let apiRequest: T let urlSession: URLSession
init(apiRequest: T, urlSession: URLSession = .shared) { self.apiRequest = apiRequest self.urlSession = urlSession }}
![Page 33: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/33.jpg)
protocol APIRequest { associatedtype RequestDataType associatedtype ResponseDataType
func makeRequest(from data: RequestDataType) throws -> URLRequest func parseResponse(data: Data) throws -> ResponseDataType}
class APIRequestLoader<T: APIRequest> { let apiRequest: T let urlSession: URLSession
init(apiRequest: T, urlSession: URLSession = .shared) { self.apiRequest = apiRequest self.urlSession = urlSession }}
![Page 34: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/34.jpg)
protocol APIRequest { associatedtype RequestDataType associatedtype ResponseDataType
func makeRequest(from data: RequestDataType) throws -> URLRequest func parseResponse(data: Data) throws -> ResponseDataType}
class APIRequestLoader<T: APIRequest> { let apiRequest: T let urlSession: URLSession
init(apiRequest: T, urlSession: URLSession = .shared) { self.apiRequest = apiRequest self.urlSession = urlSession }}
![Page 35: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/35.jpg)
protocol APIRequest { associatedtype RequestDataType associatedtype ResponseDataType
func makeRequest(from data: RequestDataType) throws -> URLRequest func parseResponse(data: Data) throws -> ResponseDataType}
class APIRequestLoader<T: APIRequest> { let apiRequest: T let urlSession: URLSession
init(apiRequest: T, urlSession: URLSession = .shared) { self.apiRequest = apiRequest self.urlSession = urlSession }}
![Page 36: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/36.jpg)
class APIRequestLoader<T: APIRequest> { func loadAPIRequest(requestData: T.RequestDataType, completionHandler: @escaping (T.ResponseDataType?, Error?) -> Void) { do { let urlRequest = try apiRequest.makeRequest(from: requestData) urlSession.dataTask(with: urlRequest) { data, response, error in guard let data = data else { return completionHandler(nil, error) } do { let parsedResponse = try self.apiRequest.parseResponse(data: data) completionHandler(parsedResponse, nil) } catch { completionHandler(nil, error) } }.resume() } catch { return completionHandler(nil, error) } }}
![Page 37: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/37.jpg)
class APIRequestLoader<T: APIRequest> { func loadAPIRequest(requestData: T.RequestDataType, completionHandler: @escaping (T.ResponseDataType?, Error?) -> Void) { do { let urlRequest = try apiRequest.makeRequest(from: requestData) urlSession.dataTask(with: urlRequest) { data, response, error in guard let data = data else { return completionHandler(nil, error) } do { let parsedResponse = try self.apiRequest.parseResponse(data: data) completionHandler(parsedResponse, nil) } catch { completionHandler(nil, error) } }.resume() } catch { return completionHandler(nil, error) } }}
![Page 38: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/38.jpg)
class APIRequestLoader<T: APIRequest> { func loadAPIRequest(requestData: T.RequestDataType, completionHandler: @escaping (T.ResponseDataType?, Error?) -> Void) { do { let urlRequest = try apiRequest.makeRequest(from: requestData) urlSession.dataTask(with: urlRequest) { data, response, error in guard let data = data else { return completionHandler(nil, error) } do { let parsedResponse = try self.apiRequest.parseResponse(data: data) completionHandler(parsedResponse, nil) } catch { completionHandler(nil, error) } }.resume() } catch { return completionHandler(nil, error) } }}
![Page 39: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/39.jpg)
class APIRequestLoader<T: APIRequest> { func loadAPIRequest(requestData: T.RequestDataType, completionHandler: @escaping (T.ResponseDataType?, Error?) -> Void) { do { let urlRequest = try apiRequest.makeRequest(from: requestData) urlSession.dataTask(with: urlRequest) { data, response, error in guard let data = data else { return completionHandler(nil, error) } do { let parsedResponse = try self.apiRequest.parseResponse(data: data) completionHandler(parsedResponse, nil) } catch { completionHandler(nil, error) } }.resume() } catch { return completionHandler(nil, error) } }}
![Page 40: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/40.jpg)
Unit Tests
App Update ViewPrepare URLRequest Parse Response
Server
App
Create URLSession Task
![Page 41: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/41.jpg)
Integration Tests
App Update View
Server
App
Create URLSession TaskPrepare URLRequest Parse Response
![Page 42: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/42.jpg)
How to Use URLProtocol
URLSession URLSessionConfiguration
![Page 43: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/43.jpg)
How to Use URLProtocol
URLSession
URLSessionDataTask
URLSessionConfiguration
![Page 44: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/44.jpg)
How to Use URLProtocol
URLProtocol subclassesURLProtocol subclassesURLProtocol subclasses
URLSession
URLSessionDataTask
URLSessionConfiguration
![Page 45: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/45.jpg)
How to Use URLProtocol
URLProtocol subclassesURLProtocol subclassesURLProtocol subclasses
URLSession
URLSessionDataTask
Built-In Protocols (e.g. HTTPS)
URLSessionConfiguration
![Page 46: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/46.jpg)
How to Use URLProtocol
URLProtocol subclassesURLProtocol subclassesURLProtocol subclasses
URLSession
URLSessionDataTask
Built-In Protocols (e.g. HTTPS)
URLSessionConfiguration
Mock Protocol
![Page 47: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/47.jpg)
How to Use URLProtocol
URLProtocol subclassesURLProtocol subclassesURLProtocol subclasses
URLSession
URLSessionDataTask
Built-In Protocols (e.g. HTTPS)
URLSessionConfiguration
Mock Protocol
URLProtocolClient
![Page 48: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/48.jpg)
class MockURLProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { return true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { // ... }
override func stopLoading() { // ... }}
![Page 49: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/49.jpg)
class MockURLProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { return true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { // ... }
override func stopLoading() { // ... }}
![Page 50: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/50.jpg)
class MockURLProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { return true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { // ... }
override func stopLoading() { // ... }}
![Page 51: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/51.jpg)
class MockURLProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { return true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { // ... }
override func stopLoading() { // ... }}
![Page 52: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/52.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 53: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/53.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 54: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/54.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 55: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/55.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 56: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/56.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 57: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/57.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 58: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/58.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 59: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/59.jpg)
class MockURLProtocol: URLProtocol { static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { XCTFail("Received unexpected request with no handler set") return } do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data) client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } }
![Page 60: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/60.jpg)
class APILoaderTests: XCTestCase { var loader: APIRequestLoader<PointsOfInterestRequest>!
override func setUp() { let request = PointsOfInterestRequest()
let configuration = URLSessionConfiguration.ephemeral configuration.protocolClasses = [MockURLProtocol.self] let urlSession = URLSession(configuration: configuration)
loader = APIRequestLoader(apiRequest: request, urlSession: urlSession) } }
![Page 61: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/61.jpg)
class APILoaderTests: XCTestCase { var loader: APIRequestLoader<PointsOfInterestRequest>!
override func setUp() { let request = PointsOfInterestRequest()
let configuration = URLSessionConfiguration.ephemeral configuration.protocolClasses = [MockURLProtocol.self] let urlSession = URLSession(configuration: configuration)
loader = APIRequestLoader(apiRequest: request, urlSession: urlSession) } }
![Page 62: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/62.jpg)
class APILoaderTests: XCTestCase { var loader: APIRequestLoader<PointsOfInterestRequest>!
override func setUp() { let request = PointsOfInterestRequest()
let configuration = URLSessionConfiguration.ephemeral configuration.protocolClasses = [MockURLProtocol.self] let urlSession = URLSession(configuration: configuration)
loader = APIRequestLoader(apiRequest: request, urlSession: urlSession) } }
![Page 63: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/63.jpg)
class APILoaderTests: XCTestCase { func testLoaderSuccess() { let inputCoordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893) let mockJSONData = "[{\"name\":\"MyPointOfInterest\"}]".data(using: .utf8)! MockURLProtocol.requestHandler = { request in XCTAssertEqual(request.url?.query?.contains("lat=37.3293"), true) return (HTTPURLResponse(), mockJSONData) }
let expectation = XCTestExpectation(description: "response") loader.loadAPIRequest(requestData: inputCoordinate) { pointsOfInterest, error in XCTAssertEqual(pointsOfInterest, [PointOfInterest(name: "MyPointOfInterest")]) expectation.fulfill() } wait(for: [expectation], timeout: 1) } }
![Page 64: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/64.jpg)
class APILoaderTests: XCTestCase { func testLoaderSuccess() { let inputCoordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893) let mockJSONData = "[{\"name\":\"MyPointOfInterest\"}]".data(using: .utf8)! MockURLProtocol.requestHandler = { request in XCTAssertEqual(request.url?.query?.contains("lat=37.3293"), true) return (HTTPURLResponse(), mockJSONData) }
let expectation = XCTestExpectation(description: "response") loader.loadAPIRequest(requestData: inputCoordinate) { pointsOfInterest, error in XCTAssertEqual(pointsOfInterest, [PointOfInterest(name: "MyPointOfInterest")]) expectation.fulfill() } wait(for: [expectation], timeout: 1) } }
![Page 65: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/65.jpg)
class APILoaderTests: XCTestCase { func testLoaderSuccess() { let inputCoordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893) let mockJSONData = "[{\"name\":\"MyPointOfInterest\"}]".data(using: .utf8)! MockURLProtocol.requestHandler = { request in XCTAssertEqual(request.url?.query?.contains("lat=37.3293"), true) return (HTTPURLResponse(), mockJSONData) }
let expectation = XCTestExpectation(description: "response") loader.loadAPIRequest(requestData: inputCoordinate) { pointsOfInterest, error in XCTAssertEqual(pointsOfInterest, [PointOfInterest(name: "MyPointOfInterest")]) expectation.fulfill() } wait(for: [expectation], timeout: 1) } }
![Page 66: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/66.jpg)
class APILoaderTests: XCTestCase { func testLoaderSuccess() { let inputCoordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893) let mockJSONData = "[{\"name\":\"MyPointOfInterest\"}]".data(using: .utf8)! MockURLProtocol.requestHandler = { request in XCTAssertEqual(request.url?.query?.contains("lat=37.3293"), true) return (HTTPURLResponse(), mockJSONData) }
let expectation = XCTestExpectation(description: "response") loader.loadAPIRequest(requestData: inputCoordinate) { pointsOfInterest, error in XCTAssertEqual(pointsOfInterest, [PointOfInterest(name: "MyPointOfInterest")]) expectation.fulfill() } wait(for: [expectation], timeout: 1) } }
![Page 67: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/67.jpg)
class APILoaderTests: XCTestCase { func testLoaderSuccess() { let inputCoordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893) let mockJSONData = "[{\"name\":\"MyPointOfInterest\"}]".data(using: .utf8)! MockURLProtocol.requestHandler = { request in XCTAssertEqual(request.url?.query?.contains("lat=37.3293"), true) return (HTTPURLResponse(), mockJSONData) }
let expectation = XCTestExpectation(description: "response") loader.loadAPIRequest(requestData: inputCoordinate) { pointsOfInterest, error in XCTAssertEqual(pointsOfInterest, [PointOfInterest(name: "MyPointOfInterest")]) expectation.fulfill() } wait(for: [expectation], timeout: 1) } }
![Page 68: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/68.jpg)
class APILoaderTests: XCTestCase { func testLoaderSuccess() { let inputCoordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893) let mockJSONData = "[{\"name\":\"MyPointOfInterest\"}]".data(using: .utf8)! MockURLProtocol.requestHandler = { request in XCTAssertEqual(request.url?.query?.contains("lat=37.3293"), true) return (HTTPURLResponse(), mockJSONData) }
let expectation = XCTestExpectation(description: "response") loader.loadAPIRequest(requestData: inputCoordinate) { pointsOfInterest, error in XCTAssertEqual(pointsOfInterest, [PointOfInterest(name: "MyPointOfInterest")]) expectation.fulfill() } wait(for: [expectation], timeout: 1) } }
![Page 69: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/69.jpg)
class APILoaderTests: XCTestCase { func testLoaderSuccess() { let inputCoordinate = CLLocationCoordinate2D(latitude: 37.3293, longitude: -121.8893) let mockJSONData = "[{\"name\":\"MyPointOfInterest\"}]".data(using: .utf8)! MockURLProtocol.requestHandler = { request in XCTAssertEqual(request.url?.query?.contains("lat=37.3293"), true) return (HTTPURLResponse(), mockJSONData) }
let expectation = XCTestExpectation(description: "response") loader.loadAPIRequest(requestData: inputCoordinate) { pointsOfInterest, error in XCTAssertEqual(pointsOfInterest, [PointOfInterest(name: "MyPointOfInterest")]) expectation.fulfill() } wait(for: [expectation], timeout: 1) } }
![Page 70: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/70.jpg)
Integration Tests
App
App
Server
Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
![Page 71: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/71.jpg)
App Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
Server
End-to-End Tests
App
![Page 72: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/72.jpg)
App Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
Server
End-to-End Tests
UI Testing in Xcode WWDC 2015
App
![Page 73: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/73.jpg)
App Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
Server
End-to-End Tests
App
![Page 74: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/74.jpg)
App Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
ServerMock Server
End-to-End Tests
App
![Page 75: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/75.jpg)
End-to-End Tests
App
App
Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
Mock Server
![Page 76: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/76.jpg)
End-to-End Tests
App
App
Update ViewCreate URLSession TaskPrepare URLRequest Parse Response
Mock ServerServer
![Page 77: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/77.jpg)
Testing Network Requests
Decompose code for testability
URLProtocol as a mocking tool
Tiered testing strategy
![Page 78: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/78.jpg)
•Testing network requests •Working with notifications •Mocking with protocols •Test execution speed
![Page 79: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/79.jpg)
Working with Notifications
Test that subject properly observes or posts a Notification
Important to test notifications in isolation
Isolation avoids unreliable or “flaky” tests
![Page 80: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/80.jpg)
class PointsOfInterestTableViewController {
var observer: AnyObject?
init() {
let name = CurrentLocationProvider.authChangedNotification
observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
self?.handleAuthChanged()
}
}
var didHandleNotification = false
func handleAuthChanged() {
didHandleNotification = true
}
}
![Page 81: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/81.jpg)
class PointsOfInterestTableViewController {
var observer: AnyObject?
init() {
let name = CurrentLocationProvider.authChangedNotification
observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
self?.handleAuthChanged()
}
}
var didHandleNotification = false
func handleAuthChanged() {
didHandleNotification = true
}
}
![Page 82: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/82.jpg)
class PointsOfInterestTableViewController {
var observer: AnyObject?
init() {
let name = CurrentLocationProvider.authChangedNotification
observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
self?.handleAuthChanged()
}
}
var didHandleNotification = false
func handleAuthChanged() {
didHandleNotification = true
}
}
![Page 83: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/83.jpg)
class PointsOfInterestTableViewController {
var observer: AnyObject?
init() {
let name = CurrentLocationProvider.authChangedNotification
observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
self?.handleAuthChanged()
}
}
var didHandleNotification = false
func handleAuthChanged() {
didHandleNotification = true
}
}
![Page 84: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/84.jpg)
class PointsOfInterestTableViewControllerTests: XCTestCase {
func testNotification() {
let observer = PointsOfInterestTableViewController()
XCTAssertFalse(observer.didHandleNotification)
// This notification will be received by all objects in process,
// could have unintended consequences
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: nil)
XCTAssertTrue(observer.didHandleNotification)
}
}
![Page 85: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/85.jpg)
class PointsOfInterestTableViewControllerTests: XCTestCase {
func testNotification() {
let observer = PointsOfInterestTableViewController()
XCTAssertFalse(observer.didHandleNotification)
// This notification will be received by all objects in process,
// could have unintended consequences
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: nil)
XCTAssertTrue(observer.didHandleNotification)
}
}
![Page 86: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/86.jpg)
Testing Notification Observers
How to use • Create separate NotificationCenter, instead of .default • Pass to init() and store in a new property • Replace all uses of NotificationCenter.default with new property
Limits scope of tests by avoiding external effects
![Page 87: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/87.jpg)
class PointsOfInterestTableViewController {
var observer: AnyObject?
init() {
let name = CurrentLocationProvider.authChangedNotification
observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
self?.handleAuthChanged()
}
}
var didHandleNotification = false
func handleAuthChanged() {
didHandleNotification = true
}
![Page 88: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/88.jpg)
class PointsOfInterestTableViewController {
let notificationCenter: NotificationCenter
var observer: AnyObject?
init(notificationCenter: NotificationCenter) {
self.notificationCenter = notificationCenter
let name = CurrentLocationProvider.authChangedNotification
observer = notificationCenter.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
self?.handleAuthChanged()
}
}
var didHandleNotification = false
func handleAuthChanged() {
didHandleNotification = true
}
![Page 89: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/89.jpg)
class PointsOfInterestTableViewController {
let notificationCenter: NotificationCenter
var observer: AnyObject?
init(notificationCenter: NotificationCenter = .default) {
self.notificationCenter = notificationCenter
let name = CurrentLocationProvider.authChangedNotification
observer = notificationCenter.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
self?.handleAuthChanged()
}
}
var didHandleNotification = false
func handleAuthChanged() {
didHandleNotification = true
}
![Page 90: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/90.jpg)
class PointsOfInterestTableViewControllerTests: XCTestCase {
func testNotification() {
let observer = PointsOfInterestTableViewController()
XCTAssertFalse(observer.didHandleNotification)
// This notification will be received by all objects in process,
// could have unintended consequences
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: nil)
XCTAssertTrue(observer.didHandleNotification)
}
}
![Page 91: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/91.jpg)
class PointsOfInterestTableViewControllerTests: XCTestCase {
func testNotification() {
let notificationCenter = NotificationCenter()
let observer = PointsOfInterestTableViewController(notificationCenter: notificationCenter)
XCTAssertFalse(observer.didHandleNotification)
// Notification posted to just this center, isolating the test
let name = CurrentLocationProvider.authChangedNotification
notificationCenter.post(name: name, object: nil)
XCTAssertTrue(observer.didHandleNotification)
}
}
![Page 92: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/92.jpg)
Testing Notification Posters
How to validate that a subject posts a Notification?
Use a separate NotificationCenter again
Use XCTNSNotificationExpectation
![Page 93: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/93.jpg)
class CurrentLocationProvider {
static let authChangedNotification = Notification.Name("AuthChanged")
func notifyAuthChanged() {
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: self)
}
}
![Page 94: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/94.jpg)
class CurrentLocationProvider {
static let authChangedNotification = Notification.Name("AuthChanged")
func notifyAuthChanged() {
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: self)
}
}
![Page 95: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/95.jpg)
class CurrentLocationProviderTests: XCTestCase { func testNotifyAuthChanged() { let poster = CurrentLocationProvider() var observer: AnyObject? let expectation = self.expectation(description: "auth changed notification")
// Uses default NotificationCenter, not properly isolating test let name = CurrentLocationProvider.authChangedNotification observer = NotificationCenter.default.addObserver(forName: name, object: poster, queue: .main) { _ in NotificationCenter.default.removeObserver(observer!) expectation.fulfill() }
poster.notifyAuthChanged() wait(for: [expectation], timeout: 0) }
![Page 96: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/96.jpg)
class CurrentLocationProviderTests: XCTestCase { func testNotifyAuthChanged() { let poster = CurrentLocationProvider() var observer: AnyObject? let expectation = self.expectation(description: "auth changed notification")
// Uses default NotificationCenter, not properly isolating test let name = CurrentLocationProvider.authChangedNotification observer = NotificationCenter.default.addObserver(forName: name, object: poster, queue: .main) { _ in NotificationCenter.default.removeObserver(observer!) expectation.fulfill() }
poster.notifyAuthChanged() wait(for: [expectation], timeout: 0) }
![Page 97: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/97.jpg)
class CurrentLocationProviderTests: XCTestCase { func testNotifyAuthChanged() {
let poster = CurrentLocationProvider()
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: poster)
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
![Page 98: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/98.jpg)
class CurrentLocationProvider {
static let authChangedNotification = Notification.Name("AuthChanged")
func notifyAuthChanged() {
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: self)
}
}
![Page 99: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/99.jpg)
class CurrentLocationProvider {
static let authChangedNotification = Notification.Name("AuthChanged")
let notificationCenter: NotificationCenter
init(notificationCenter: NotificationCenter = .default) {
self.notificationCenter = notificationCenter
}
func notifyAuthChanged() {
let name = CurrentLocationProvider.authChangedNotification
notificationCenter.post(name: name, object: self)
}
}
![Page 100: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/100.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testNotifyAuthChanged() {
let poster = CurrentLocationProvider()
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: poster)
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
}
![Page 101: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/101.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testNotifyAuthChanged() {
let notificationCenter = NotificationCenter()
let poster = CurrentLocationProvider(notificationCenter: notificationCenter)
// Notification only sent to this specific center, isolating test
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: poster, notificationCenter: notificationCenter)
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
}
![Page 102: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/102.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testNotifyAuthChanged() {
let notificationCenter = NotificationCenter()
let poster = CurrentLocationProvider(notificationCenter: notificationCenter)
// Notification only sent to this specific center, isolating test
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: poster, notificationCenter: notificationCenter)
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
}
![Page 103: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/103.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testNotifyAuthChanged() {
let notificationCenter = NotificationCenter()
let poster = CurrentLocationProvider(notificationCenter: notificationCenter)
// Notification only sent to this specific center, isolating test
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: poster, notificationCenter: notificationCenter)
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
}
![Page 104: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/104.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testNotifyAuthChanged() {
let notificationCenter = NotificationCenter()
let poster = CurrentLocationProvider(notificationCenter: notificationCenter)
// Notification only sent to this specific center, isolating test
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: poster, notificationCenter: notificationCenter)
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
}
![Page 105: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/105.jpg)
•Testing network requests •Working with notifications •Mocking with protocols •Test execution speed
![Page 106: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/106.jpg)
Mocking with Protocols
Classes often interact with other classes in app or SDK
Many SDK classes cannot be created directly
Delegate protocols make testing more challenging
Solution: Mock interface of external class using protocol
![Page 107: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/107.jpg)
import CoreLocation
class CurrentLocationProvider: NSObject {
let locationManager = CLLocationManager()
override init() {
super.init()
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
self.locationManager.delegate = self
}
// ...
![Page 108: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/108.jpg)
class CurrentLocationProvider: NSObject {
var currentLocationCheckCallback: ((CLLocation) -> Void)?
func checkCurrentLocation(completion: @escaping (Bool) -> Void) {
self.currentLocationCheckCallback = { [unowned self] location in
completion(self.isPointOfInterest(location))
}
locationManager.requestLocation()
}
func isPointOfInterest(_ location: CLLocation) -> Bool { // Perform check... }
}
![Page 109: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/109.jpg)
class CurrentLocationProvider: NSObject {
var currentLocationCheckCallback: ((CLLocation) -> Void)?
func checkCurrentLocation(completion: @escaping (Bool) -> Void) {
self.currentLocationCheckCallback = { [unowned self] location in
completion(self.isPointOfInterest(location))
}
locationManager.requestLocation()
}
func isPointOfInterest(_ location: CLLocation) -> Bool { // Perform check... }
}
![Page 110: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/110.jpg)
extension CurrentLocationProvider: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locs: [CLLocation]){
guard let location = locs.first else { return }
self.currentLocationCheckCallback?(location)
self.currentLocationCheckCallback = nil
}
}
![Page 111: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/111.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testCheckCurrentLocation() {
let provider = CurrentLocationProvider()
XCTAssertNotEqual(provider.locationManager.desiredAccuracy, 0)
XCTAssertNotNil(provider.locationManager.delegate)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// No way to mock the current location or confirm requestLocation() was called
wait(for: [completionExpectation], timeout: 1)
} }
![Page 112: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/112.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testCheckCurrentLocation() {
let provider = CurrentLocationProvider()
XCTAssertNotEqual(provider.locationManager.desiredAccuracy, 0)
XCTAssertNotNil(provider.locationManager.delegate)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// No way to mock the current location or confirm requestLocation() was called
wait(for: [completionExpectation], timeout: 1)
} }
![Page 113: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/113.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testCheckCurrentLocation() {
let provider = CurrentLocationProvider()
XCTAssertNotEqual(provider.locationManager.desiredAccuracy, 0)
XCTAssertNotNil(provider.locationManager.delegate)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// No way to mock the current location or confirm requestLocation() was called
wait(for: [completionExpectation], timeout: 1)
} }
![Page 114: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/114.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testCheckCurrentLocation() {
let provider = CurrentLocationProvider()
XCTAssertNotEqual(provider.locationManager.desiredAccuracy, 0)
XCTAssertNotNil(provider.locationManager.delegate)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// No way to mock the current location or confirm requestLocation() was called
wait(for: [completionExpectation], timeout: 1)
} }
![Page 115: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/115.jpg)
class CurrentLocationProviderTests: XCTestCase {
func testCheckCurrentLocation() {
let provider = CurrentLocationProvider()
XCTAssertNotEqual(provider.locationManager.desiredAccuracy, 0)
XCTAssertNotNil(provider.locationManager.delegate)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// No way to mock the current location or confirm requestLocation() was called
wait(for: [completionExpectation], timeout: 1)
} }
![Page 116: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/116.jpg)
Mocking Using a Subclass
Subclassing the external class in tests and overriding methods
Can work, but risky
Some SDK classes cannot be subclassed
Easy to forget to override methods
![Page 117: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/117.jpg)
Mocking Using a Subclass
Subclassing the external class in tests and overriding methods
Can work, but risky
Some SDK classes cannot be subclassed
Easy to forget to override methods
![Page 118: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/118.jpg)
class CurrentLocationProvider: NSObject {
let locationManager = CLLocationManager()
override init() {
super.init()
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
self.locationManager.delegate = self
} }
![Page 119: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/119.jpg)
class CurrentLocationProvider: NSObject {
let locationManager = CLLocationManager()
override init() {
super.init()
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
self.locationManager.delegate = self
} }
protocol LocationFetcher {
var delegate: CLLocationManagerDelegate? { get set }
var desiredAccuracy: CLLocationAccuracy { get set }
func requestLocation()
} extension CLLocationManager: LocationFetcher {}
![Page 120: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/120.jpg)
class CurrentLocationProvider: NSObject { var locationFetcher: LocationFetcher
init(locationFetcher: LocationFetcher) {
self.locationFetcher = locationFetcher
super.init()
self.locationFetcher.desiredAccuracy = kCLLocationAccuracyHundredMeters
self.locationFetcher.delegate = self
}
}
protocol LocationFetcher {
var delegate: CLLocationManagerDelegate? { get set }
var desiredAccuracy: CLLocationAccuracy { get set }
func requestLocation()
} extension CLLocationManager: LocationFetcher {}
![Page 121: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/121.jpg)
class CurrentLocationProvider: NSObject { var locationFetcher: LocationFetcher
init(locationFetcher: LocationFetcher = CLLocationManager()) {
self.locationFetcher = locationFetcher
super.init()
self.locationFetcher.desiredAccuracy = kCLLocationAccuracyHundredMeters
self.locationFetcher.delegate = self
}
}
protocol LocationFetcher {
var delegate: CLLocationManagerDelegate? { get set }
var desiredAccuracy: CLLocationAccuracy { get set }
func requestLocation()
} extension CLLocationManager: LocationFetcher {}
![Page 122: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/122.jpg)
class CurrentLocationProvider: NSObject {
var currentLocationCheckCallback: ((CLLocation) -> Void)?
func checkCurrentLocation(completion: @escaping (Bool) -> Void) {
self.currentLocationCheckCallback = { [unowned self] location in
completion(self.isPointOfInterest(location))
}
locationManager.requestLocation()
}
func isPointOfInterest(_ location: CLLocation) -> Bool { // Perform check... }
}
![Page 123: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/123.jpg)
class CurrentLocationProvider: NSObject {
var currentLocationCheckCallback: ((CLLocation) -> Void)?
func checkCurrentLocation(completion: @escaping (Bool) -> Void) {
self.currentLocationCheckCallback = { [unowned self] location in
completion(self.isPointOfInterest(location))
}
locationFetcher.requestLocation()
}
func isPointOfInterest(_ location: CLLocation) -> Bool { // Perform check... }
}
![Page 124: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/124.jpg)
extension CurrentLocationProvider: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locs: [CLLocation]){
guard let location = locs.first else { return }
self.currentLocationCheckCallback?(location)
self.currentLocationCheckCallback = nil
}
}
![Page 125: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/125.jpg)
protocol LocationFetcher {
var delegate: CLLocationManagerDelegate? { get set } // ...
}
extension CLLocationManager: LocationFetcher {}
![Page 126: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/126.jpg)
protocol LocationFetcher { var locationFetcherDelegate: LocationFetcherDelegate? { get set } // ... } protocol LocationFetcherDelegate: class { func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locs: [CLLocation]) }
extension CLLocationManager: LocationFetcher {}
![Page 127: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/127.jpg)
protocol LocationFetcher { var locationFetcherDelegate: LocationFetcherDelegate? { get set } // ... } protocol LocationFetcherDelegate: class { func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locs: [CLLocation]) }
extension CLLocationManager: LocationFetcher { var locationFetcherDelegate: LocationFetcherDelegate? { get { return delegate as! LocationFetcherDelegate? } set { delegate = newValue as! CLLocationManagerDelegate? }
} }
![Page 128: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/128.jpg)
protocol LocationFetcher { var locationFetcherDelegate: LocationFetcherDelegate? { get set } // ... } protocol LocationFetcherDelegate: class { func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locs: [CLLocation]) }
class CurrentLocationProvider: NSObject { var locationFetcher: LocationFetcher
init(locationFetcher: LocationFetcher = CLLocationManager()) {
// ... self.locationFetcher.delegate = self
}
}
![Page 129: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/129.jpg)
protocol LocationFetcher { var locationFetcherDelegate: LocationFetcherDelegate? { get set } // ... } protocol LocationFetcherDelegate: class { func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locs: [CLLocation]) }
class CurrentLocationProvider: NSObject { var locationFetcher: LocationFetcher
init(locationFetcher: LocationFetcher = CLLocationManager()) {
// ... self.locationFetcher.locationFetcherDelegate = self
}
}
![Page 130: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/130.jpg)
extension CurrentLocationProvider: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locs: [CLLocation]){
guard let location = locs.first else { return }
self.currentLocationCheckCallback?(location)
self.currentLocationCheckCallback = nil
}
}
![Page 131: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/131.jpg)
extension CurrentLocationProvider: LocationFetcherDelegate {
func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locs: [CLLocation]){
guard let location = locs.first else { return }
self.currentLocationCheckCallback?(location)
self.currentLocationCheckCallback = nil
}
}
![Page 132: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/132.jpg)
extension CurrentLocationProvider: LocationFetcherDelegate {
func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locs: [CLLocation]){
guard let location = locs.first else { return }
self.currentLocationCheckCallback?(location)
self.currentLocationCheckCallback = nil
}
}
extension CurrentLocationProvider: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locs: [CLLocation]){
self.locationFetcher(manager, didUpdateLocations: locs)
}
}
![Page 133: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/133.jpg)
extension CurrentLocationProvider: LocationFetcherDelegate {
func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locs: [CLLocation]){
guard let location = locs.first else { return }
self.currentLocationCheckCallback?(location)
self.currentLocationCheckCallback = nil
}
}
extension CurrentLocationProvider: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locs: [CLLocation]){
self.locationFetcher(manager, didUpdateLocations: locs)
}
}
![Page 134: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/134.jpg)
class CurrentLocationProviderTests: XCTestCase {
struct MockLocationFetcher: LocationFetcher {
weak var locationFetcherDelegate: LocationFetcherDelegate?
var desiredAccuracy: CLLocationAccuracy = 0
var handleRequestLocation: (() -> CLLocation)?
func requestLocation() {
guard let location = handleRequestLocation?() else { return }
locationFetcherDelegate?.locationFetcher(self, didUpdateLocations: [location])
}
}
![Page 135: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/135.jpg)
class CurrentLocationProviderTests: XCTestCase {
struct MockLocationFetcher: LocationFetcher {
weak var locationFetcherDelegate: LocationFetcherDelegate?
var desiredAccuracy: CLLocationAccuracy = 0
var handleRequestLocation: (() -> CLLocation)?
func requestLocation() {
guard let location = handleRequestLocation?() else { return }
locationFetcherDelegate?.locationFetcher(self, didUpdateLocations: [location])
}
}
![Page 136: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/136.jpg)
class CurrentLocationProviderTests: XCTestCase {
struct MockLocationFetcher: LocationFetcher {
weak var locationFetcherDelegate: LocationFetcherDelegate?
var desiredAccuracy: CLLocationAccuracy = 0
var handleRequestLocation: (() -> CLLocation)?
func requestLocation() {
guard let location = handleRequestLocation?() else { return }
locationFetcherDelegate?.locationFetcher(self, didUpdateLocations: [location])
}
}
![Page 137: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/137.jpg)
func testCheckCurrentLocation() {
var locationFetcher = MockLocationFetcher()
let requestLocationExpectation = expectation(description: "request location")
locationFetcher.handleRequestLocation = {
requestLocationExpectation.fulfill()
return CLLocation(latitude: 37.3293, longitude: -121.8893)
}
let provider = CurrentLocationProvider(locationFetcher: locationFetcher)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// Can mock the current location
wait(for: [requestLocationExpectation, completionExpectation], timeout: 1)
}
![Page 138: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/138.jpg)
func testCheckCurrentLocation() {
var locationFetcher = MockLocationFetcher()
let requestLocationExpectation = expectation(description: "request location")
locationFetcher.handleRequestLocation = {
requestLocationExpectation.fulfill()
return CLLocation(latitude: 37.3293, longitude: -121.8893)
}
let provider = CurrentLocationProvider(locationFetcher: locationFetcher)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// Can mock the current location
wait(for: [requestLocationExpectation, completionExpectation], timeout: 1)
}
![Page 139: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/139.jpg)
func testCheckCurrentLocation() {
var locationFetcher = MockLocationFetcher()
let requestLocationExpectation = expectation(description: "request location")
locationFetcher.handleRequestLocation = {
requestLocationExpectation.fulfill()
return CLLocation(latitude: 37.3293, longitude: -121.8893)
}
let provider = CurrentLocationProvider(locationFetcher: locationFetcher)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// Can mock the current location
wait(for: [requestLocationExpectation, completionExpectation], timeout: 1)
}
![Page 140: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/140.jpg)
func testCheckCurrentLocation() {
var locationFetcher = MockLocationFetcher()
let requestLocationExpectation = expectation(description: "request location")
locationFetcher.handleRequestLocation = {
requestLocationExpectation.fulfill()
return CLLocation(latitude: 37.3293, longitude: -121.8893)
}
let provider = CurrentLocationProvider(locationFetcher: locationFetcher)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// Can mock the current location
wait(for: [requestLocationExpectation, completionExpectation], timeout: 1)
}
![Page 141: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/141.jpg)
func testCheckCurrentLocation() {
var locationFetcher = MockLocationFetcher()
let requestLocationExpectation = expectation(description: "request location")
locationFetcher.handleRequestLocation = {
requestLocationExpectation.fulfill()
return CLLocation(latitude: 37.3293, longitude: -121.8893)
}
let provider = CurrentLocationProvider(locationFetcher: locationFetcher)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// Can mock the current location
wait(for: [requestLocationExpectation, completionExpectation], timeout: 1)
}
![Page 142: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/142.jpg)
func testCheckCurrentLocation() {
var locationFetcher = MockLocationFetcher()
let requestLocationExpectation = expectation(description: "request location")
locationFetcher.handleRequestLocation = {
requestLocationExpectation.fulfill()
return CLLocation(latitude: 37.3293, longitude: -121.8893)
}
let provider = CurrentLocationProvider(locationFetcher: locationFetcher)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// Can mock the current location
wait(for: [requestLocationExpectation, completionExpectation], timeout: 1)
}
![Page 143: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/143.jpg)
func testCheckCurrentLocation() {
var locationFetcher = MockLocationFetcher()
let requestLocationExpectation = expectation(description: "request location")
locationFetcher.handleRequestLocation = {
requestLocationExpectation.fulfill()
return CLLocation(latitude: 37.3293, longitude: -121.8893)
}
let provider = CurrentLocationProvider(locationFetcher: locationFetcher)
let completionExpectation = expectation(description: "completion")
provider.checkCurrentLocation { isPointOfInterest in
XCTAssertTrue(isPointOfInterest)
completionExpectation.fulfill()
}
// Can mock the current location
wait(for: [requestLocationExpectation, completionExpectation], timeout: 1)
}
![Page 144: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/144.jpg)
Mocking with Protocols Recap
Define a protocol representing the external interface
Define extension on the external class conforming to the protocol
Replace all usage of external class with the protocol
Set the external reference via initializer or a property, using the protocol type
![Page 145: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/145.jpg)
Mocking Delegates with Protocols Recap
Define delegate protocol with interfaces your code implements
Replace subject type with mock protocol defined earlier
In mock protocol, rename delegate property
In extension on original type, implement mock delegate property and convert
![Page 146: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/146.jpg)
•Testing network requests •Working with notifications •Mocking with protocols •Test execution speed
![Page 147: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/147.jpg)
Test Execution Speed
Slow tests hinder developer productivity
Want tests to run fast
Artificial delays should not be necessary with sufficient mocking
![Page 148: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/148.jpg)
class FeaturedPlaceManager {
var currentPlace: Place
func scheduleNextPlace() {
// Show next place after 10 seconds
Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
}
func showNextPlace() {
// Set currentPlace to next place...
}
}
![Page 149: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/149.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
let manager = FeaturedPlaceManager()
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// Slow! Not ideal
RunLoop.current.run(until: Date(timeIntervalSinceNow: 11))
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
}
![Page 150: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/150.jpg)
class FeaturedPlaceManager {
var currentPlace: Place
func scheduleNextPlace() {
Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
}
func showNextPlace() {
// Set currentPlace to next place...
}
}
![Page 151: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/151.jpg)
class FeaturedPlaceManager {
var currentPlace: Place
var interval: TimeInterval = 10
func scheduleNextPlace() {
Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
}
func showNextPlace() {
// Set currentPlace to next place...
}
}
![Page 152: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/152.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
let manager = FeaturedPlaceManager()
manager.interval = 1
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// Less slow but still timing-dependent
RunLoop.current.run(until: Date(timeIntervalSinceNow: 2))
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
}
![Page 153: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/153.jpg)
Testing Delayed Actions Without the delay
How to use • Identify the “delay” technique (e.g. Timer, DispatchQueue.asyncAfter) • Mock this mechanism during tests • Invoke delayed action immediately
![Page 154: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/154.jpg)
class FeaturedPlaceManager {
func scheduleNextPlace() {
Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
}
func showNextPlace() { /* ... */ }
}
![Page 155: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/155.jpg)
class FeaturedPlaceManager {
func scheduleNextPlace() {
Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
}
func showNextPlace() { /* ... */ }
}
![Page 156: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/156.jpg)
class FeaturedPlaceManager {
let runLoop = RunLoop.current
func scheduleNextPlace() {
let timer = Timer(timeInterval: 10, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
runLoop.add(timer, forMode: .default)
}
func showNextPlace() { /* ... */ }
}
![Page 157: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/157.jpg)
protocol TimerScheduler {
func add(_ timer: Timer, forMode mode: RunLoop.Mode)
}
extension RunLoop: TimerScheduler {}
![Page 158: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/158.jpg)
class FeaturedPlaceManager {
let runLoop = RunLoop.current
func scheduleNextPlace() {
let timer = Timer(timeInterval: 10, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
runLoop.add(timer, forMode: .default)
}
func showNextPlace() { /* ... */ }
}
![Page 159: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/159.jpg)
class FeaturedPlaceManager {
let timerScheduler: TimerScheduler
init(timerScheduler: TimerScheduler) {
self.timerScheduler = timerScheduler
}
func scheduleNextPlace() {
let timer = Timer(timeInterval: 10, repeats: false) { [weak self] _ in
self?.showNextPlace()
}
timerScheduler.add(timer, forMode: .default)
}
func showNextPlace() { /* ... */ }
}
![Page 160: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/160.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
struct MockTimerScheduler: TimerScheduler {
var handleAddTimer: ((_ timer: Timer) -> Void)?
func add(_ timer: Timer, forMode mode: RunLoop.Mode) {
handleAddTimer?(timer)
}
}
// ...
![Page 161: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/161.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
var timerScheduler = MockTimerScheduler()
var timerDelay = TimeInterval(0)
timerScheduler.handleAddTimer = { timer in
timerDelay = timer.fireDate.timeIntervalSinceNow
timer.fire()
}
let manager = FeaturedPlaceManager(timerScheduler: timerScheduler)
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// No delay!
XCTAssertEqual(timerDelay, 10, accuracy: 1)
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
![Page 162: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/162.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
var timerScheduler = MockTimerScheduler()
var timerDelay = TimeInterval(0)
timerScheduler.handleAddTimer = { timer in
timerDelay = timer.fireDate.timeIntervalSinceNow
timer.fire()
}
let manager = FeaturedPlaceManager(timerScheduler: timerScheduler)
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// No delay!
XCTAssertEqual(timerDelay, 10, accuracy: 1)
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
![Page 163: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/163.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
var timerScheduler = MockTimerScheduler()
var timerDelay = TimeInterval(0)
timerScheduler.handleAddTimer = { timer in
timerDelay = timer.fireDate.timeIntervalSinceNow
timer.fire()
}
let manager = FeaturedPlaceManager(timerScheduler: timerScheduler)
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// No delay!
XCTAssertEqual(timerDelay, 10, accuracy: 1)
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
![Page 164: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/164.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
var timerScheduler = MockTimerScheduler()
var timerDelay = TimeInterval(0)
timerScheduler.handleAddTimer = { timer in
timerDelay = timer.fireDate.timeIntervalSinceNow
timer.fire()
}
let manager = FeaturedPlaceManager(timerScheduler: timerScheduler)
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// No delay!
XCTAssertEqual(timerDelay, 10, accuracy: 1)
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
![Page 165: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/165.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
var timerScheduler = MockTimerScheduler()
var timerDelay = TimeInterval(0)
timerScheduler.handleAddTimer = { timer in
timerDelay = timer.fireDate.timeIntervalSinceNow
timer.fire()
}
let manager = FeaturedPlaceManager(timerScheduler: timerScheduler)
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// No delay!
XCTAssertEqual(timerDelay, 10, accuracy: 1)
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
![Page 166: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/166.jpg)
class FeaturedPlaceManagerTests: XCTestCase {
func testScheduleNextPlace() {
var timerScheduler = MockTimerScheduler()
var timerDelay = TimeInterval(0)
timerScheduler.handleAddTimer = { timer in
timerDelay = timer.fireDate.timeIntervalSinceNow
timer.fire()
}
let manager = FeaturedPlaceManager(timerScheduler: timerScheduler)
let beforePlace = manager.currentPlace
manager.scheduleNextPlace()
// No delay!
XCTAssertEqual(timerDelay, 10, accuracy: 1)
XCTAssertNotEqual(manager.currentPlace, beforePlace)
}
![Page 167: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/167.jpg)
Testing Delayed Actions
Delay is eliminated using this technique
Majority of tests should be direct, without mocking delays
![Page 168: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/168.jpg)
Setting Expectations
Be mindful when using XCTNSPredicateExpectation
Slower and best suited for UI tests
Use faster, callback-based expectations in unit tests: • XCTestExpectation • XCTNSNotificationExpectation • XCTKVOExpectation
![Page 169: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/169.jpg)
Optimizing App Launch When Testing
Avoid unnecessary work when app is launched as unit test host
Testing begins after ‘app did finish launching’
Set custom scheme environment variables/launch arguments for testing
![Page 170: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/170.jpg)
Optimizing App Launch When Testing
![Page 171: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/171.jpg)
Optimizing App Launch When Testing
func application(_ application: UIApplication, didFinishLaunchingWithOptions opts: …) -> Bool {
let isUnitTesting = ProcessInfo.processInfo.environment["IS_UNIT_TESTING"] == "1"
if isUnitTesting == false {
// Do UI-related setup, which can be skipped when testing
}
return true
}
![Page 172: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/172.jpg)
Summary
Testing network requests
Working with notifications
Mocking with protocols
Test execution speed
![Page 173: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/173.jpg)
More Informationhttps://developer.apple.com/wwdc18/417
![Page 174: Testing Tips & Tricks€¦ · •Testing network requests • Working with notifications • Mocking with protocols • Test execution speed • Testing network requests](https://reader030.vdocuments.site/reader030/viewer/2022011921/6036434ba668d65a4520767d/html5/thumbnails/174.jpg)