Skip to content

Instantly share code, notes, and snippets.

@JohnSundell
Last active October 26, 2017 18:23
Show Gist options
  • Save JohnSundell/0163219c7438f6b6656bb5455ec62d76 to your computer and use it in GitHub Desktop.
Save JohnSundell/0163219c7438f6b6656bb5455ec62d76 to your computer and use it in GitHub Desktop.
This is a great technique if you need to interact with singleton-based APIs but still have great testability
import UIKit
// Create a protocol that defines what APIs that you need from the singleton
protocol Application {
func open(url: URL)
}
// Make the singleton-based class conform to your protocol
extension UIApplication: Application {
func open(url: URL) {
open(url, options: [:], completionHandler: nil)
}
}
class Navigator {
private let application: Application
// Use a default argument to avoid increasing the complexity of the API, while still enabling dependency injection
init(application: Application = UIApplication.shared) {
self.application = application
}
func navigate(to url: URL) {
application.open(url: url)
}
}
// Our Navigator class can now be used like this in our production code:
let navigator = Navigator()
// And in our tests, we can inject a mocked Application
class ApplicationMock: Application {
private(set) var openedURLs = [URL]()
func open(url: URL) {
openedURLs.append(url)
}
}
let application = ApplicationMock()
let navigator = Navigator(application: application)
let url = URL(string: "https://github.com")!
navigator.navigate(to: url)
XCTAssertEqual(application.openedURLs, [url])
@shepting
Copy link

shepting commented Feb 8, 2017

Great stuff!

@kdawgwilk
Copy link

kdawgwilk commented Feb 9, 2017

That seems easy for functions with no return value but what if it is something like

public func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> ()) -> URLSessionDataTask

from URLSession singleton

@cojoj
Copy link

cojoj commented Feb 16, 2017

@kdawgwilk I think you can return anything. URLSessionDataTask is returned immediately, right? So why not pass it back to protocol method?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment