this is not a question regarding that should we use singleton or not. but rather mocking singleton related.
this is just a sample example, as i was reading about mocking singleton is tough. so i thought let me give a try.
i am able to mock it but not sure is this a correct approach ?
protocol APIManagerProtocol {
static var sharedManager: APIManagerProtocol {get set}
func doThis()
}
class APIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = APIManager()
private init() {
}
func doThis() {
}
}
class ViewController: UIViewController {
private var apiManager: APIManagerProtocol?
override func viewDidLoad() {
}
convenience init(_ apimanager: APIManagerProtocol){
self.init()
apiManager = apimanager
}
func DoSomeRandomStuff(){
apiManager?.doThis()
}
}
import Foundation
#testable import SingleTonUnitTesting
class MockAPIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = MockAPIManager()
var isdoThisCalled = false
func doThis(){
isdoThisCalled = true
}
private init(){
}
}
class ViewControllerTests: XCTestCase {
var sut: ViewController?
var mockAPIManager: MockAPIManager?
override func setUp() {
mockAPIManager = MockAPIManager.sharedManager as? MockAPIManager
sut = ViewController(mockAPIManager!)
}
func test_viewController_doSomeRandomStuffs(){
sut?.DoSomeRandomStuff()
XCTAssertTrue(mockAPIManager!.isdoThisCalled)
}
override func tearDown() {
sut = nil
mockAPIManager = nil
}
}
The basic idea is right: Avoid repeated references to the singleton directly throughout the code, but rather inject object that conforms to the protocol.
What’s not quite right is that you are testing something internal to the MockAPIManager class. The mock is only there to serve a broader goal, namely to test your business logic (without external dependencies). So, ideally, you should be testing something that is exposed by APIManagerProtocol (or some logical result of that).
So, let’s make this concrete: For example, let’s assume your API had some method to retrieve the age of a user from a web service:
public protocol APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void)
}
(Note, by the way, that the static singleton method doesn’t belong in the protocol. It’s an implementation detail of the API manager, not part of the protocol. No controllers that get a manager injected will ever need to call shared/sharedManager themselves.)
And lets assume that your view controller (or perhaps better, its view model/presenter) had a method to retrieve the age and create an appropriate message to be shown in the UI:
func buildAgeMessage(for userid: String, completion: #escaping (String) -> Void) {
apiManager?.fetchAge(for: userid) { result in
switch result {
case .failure:
completion("Error retrieving age.")
case .success(let age):
completion("The user is \(age) years old.")
}
}
}
The API manager mock would then implement the method:
class MockAPIManager: APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void) {
switch userid {
case "123":
completion(.success(42))
default:
completion(.failure(APIManagerError.notFound))
}
}
}
Then you could test the logic of building this string to be shown in your UI, using the mocked API rather than the actual network service:
class ViewControllerTests: XCTestCase {
var viewController: ViewController?
override func setUp() {
viewController = ViewController(MockAPIManager())
}
func testSuccessfulAgeMessage() {
let e = expectation(description: "testSuccessfulAgeMessage")
viewController?.buildAgeMessage(for: "123") { string in
XCTAssertEqual(string, "The user is 42 years old.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
func testFailureAgeMessage() {
let e = expectation(description: "testFailureAgeMessage")
viewController?.buildAgeMessage(for: "xyz") { string in
XCTAssertEqual(string, "Error retrieving age.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
}
i was reading about mocking singleton is tough
The notion is that if you have these APIManager.shared references sprinkled throughout your code, it’s harder to swap them out with the mock object. Injecting solves this problem.
Then, again, if you’ve now injected this APIManager instance everywhere to facilitate mocking and have eliminate all of these shared references, it begs the question that you wanted to avoid, namely why use a singleton anymore?
Related
I am trying to unit test a class that has different modes of setup.
class Controller{
enum Mode{
case listing
case pages(String?)
}
private (set) var mode : Mode = .listing
private (set) var models = [Model]()
init() {
...
}
init(id : String) {
mode = .pages(id)
}
func fetchInfo(){
switch mode{
case .listing:
ApiManager.firstNetworkCall(){ (json, error) in ...
setupModel()
}
case .pages(let id):
ApiManager.secondNetworkCall(id : id){ (json, error) in ...
setupModel()
}
}
}
}
Both of these will update the models array with different quantity of data.
What I have right now:
var controller : Controller!
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
controller = Controller()
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
controller = nil
try super.tearDownWithError()
}
func testDefaultListingMode() throws {
switch controller.mode{
case .listing:
XCTAssertTrue(true)
default:
XCTAssertFalse(false)
}
}
func testAPISetup() throws {
controller.fetchInfo()
//now what?
}
This checks if the mode is correct but I am trying to go one step further and check if the correct number of items is setup depending on the mode. And want to call the fetchInfo() method directly from the XCTestCase and just validate the model count.
All the tutorials and guides I have seen just talk about faking the behaviour with a URLSession. But the API call is dependent on the mode that happens as an internal check inside the fetchInfo method and is the only method exposed to other classes. I would simply like to test the method (in case something breaks inside that method causing a bug).
How do I go about doing that? I can't figure out how to complete the testAPISetup() method.
What I had for networking:
class NetworkingManager{
static var alamoFireManager = Session.default
static func POST(...., completion : ()->()) {
sendRequest(....., completion : completion)
}
private static func sendRequest(...., completion : ()->()) {
let request = alamoFireManager.request(.....)
request.responseJSON{
completion()
}
}
}
class APIManager{
static func firstNetworkCall(completion : ()->()){
NetworkingManager.POST(..., completion : completion)
}
}
I had to change the above and removed all mentions of static and singletons. I decided to go ahead with using class inheritance. I tried to avoid it and use protocols but it frankly was quite easier to use classes!
class NetworkingManager{
private (set) var sessionManager: Session
init(config : URLSessionConfiguration = .default){
config.timeoutIntervalForResource = 8.0
config.timeoutIntervalForRequest = 8.0
self.sessionManager = Session(configuration: config)
}
func request(...) {
//hit alamofire
}
}
class APIManager : NetworkingManager{
override init(config: URLSessionConfiguration = .default) {
super.init(config: config)
}
//other methods
...
}
class Controller{
private let apiManager : APIManager
init(manager : APIManager = APIManager()){
self.apiManager = manager
}
}
And in my test class:
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
let config = URLSessionConfiguration.ephemeral
apiManager = APIManager(config : config)
controller = Controller(manager : apiManager)
}
func testApiCalled() throws{
controller.fetchNecessaryInfo()
//had to add one second delay as alamofire adds the request on another queue. Wasn't able to put it on main queue.
sleep(1)
let promise = expectation(description: "Check request called")
apiManager.sessionManager.session.getAllTasks { (taskArray) in
if taskArray.count > 1{
XCTFail("Multiple requests when there should be only one")
}
if let task = taskArray.first, let request = task.currentRequest{
if let string = request.url?.absoluteString{
XCTAssert(...)
}else{
XCTFail("Incorrect URL")
}
}else{
XCTFail("Somehow no task exists. So this is an error")
}
promise.fulfill()
}
wait(for: [promise], timeout: 1.0)
}
I couldn't figure out any other way without having to instantiate an object for APIManager, so had to refactor!
Do we need to create the Web services singleton class file out of the MVC folder, while following MVC Structure or it stays in Model folder together with the Mapper classes
It is a good practice to avoid using singletons. Although they are fast to create, they are not very scalable, difficult to test, etc, Try to use dependency injections instead.
Singletons are easier to create, easier to use, but it fix you to using specific object that is difficult to replace, if needed. For example you have Store class that supplying name of current user to your VC and it had NetworkClient that fetching User from the network. You can declare both of them as Singletons. It means that any VC had to use same Store.sharedInstance, and store had to use NetworkClient .sharedInstance. Imagine that you want to create UnitTests. As your Store depends on sharedInstance of Client you can't test them separately. It is difficult to test without network, or with timeouts etc. And who's responsible if test failed. Store itself? Network client? Mapper?
In Dependecy Injection pattern instead of using singular instance of store and client, you creating variable in AppDelegate for example, an provide them to all instances you need. Store instead of using shared instance of network client, using some instance that conforms to protocol. Developer can choose what client dependency Store had to use, inject appropriate depends on situation. Usually injection happening during initialisation. In case of tests you can supply you classes with small predictable mocks.
protocol UserStoring: class {
var userName: String { get }
}
final class Store: UserStoring {
private var client: NetworkClient
init (client: NetworkClient) {
self.client = client
}
private var cachedUser: User? = nil
var userName: String {
guard let user = cachedUser else {
client.fetchUser(userId: "current") { userFromClient in
self.cachedUser = userFromClient
}
return " ... "
}
return user.name
}
}
protocol DataFetching {
func fetchUser(userId: String, handler: #escaping (User)->() )
}
final class NetworkClient: DataFetching {
private var session: URLSession
private var baseURL: URL
private var mapper: Mapper
init(session: URLSession, baseURL: URLSession, mapper: Mapper) {
self.session = session
self.baseURL = baseURL
self.mapper = mapper
}
func fetchUser(userId: String, handler: #escaping (User)->() ) {
session.dataTask(with: baseURL.appendingPathComponent("user/\(userId)")) { (data, response, error) in
let user = self.mapper.map(data)
handler(user)
}
}
}
You can even to create a singleton that will keep all shared services in one place, but as you have UIApplication singleton, so you can utilise it: UIApplication.sharedApplication.dependencyService and initialise it in didFinishLaunch.
protocol DependencyServiceSharing {
var client: DataFetching { get }
var mapper: DataMapping { get }
var store: UserStoring { get }
}
class DependencyService: DependencyServiceSharing {
let client: DataFetching
lazy var mapper: DataMapping = Mapper()
lazy var store: UserStoring = Store(client: client)
init(basePath: String) {
client = NetworkClient(session: URLSession.shared, baseURL: URL(string: basePath) , mapper: self.mapper)
}
}
protocol DependencyServiceKeeping: class {
var dependencyService: DependencyServiceSharing? { get }
}
extension UIApplication: DependencyServiceKeeping {
var dependencyService: DependencyServiceSharing? {
return (delegate as? DependencyServiceKeeping)?.dependencyService
}
}
class AppDelegate: UIResponder, UIApplicationDelegate, DependencyServiceKeeping {
var dependencyService: DependencyServiceSharing?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
dependencyService = DependencyService(basePath: "https://google.com")
...
Sorry I could not come up with better title than that, Ill modify it if anybody suggests a better one after.
I have a protocol
#objc public protocol MyCollectionViewProtocol {
func scrollViewShouldScrollToTop()
}
I have declared it to be #objc because unfortunately DelegateProxy does not work with non NSObject protocols (I assume, if somebody can clarify that, will be a great help)
My collectionView
public class MyCollectionView: UICollectionView {
weak var cvDelegate : MyCollectionViewProtocol?
... //rest of the code isnt related to this question in particular
Now I declare delegate proxy as
open class RxMyCollectionViewDelegateProxy : DelegateProxy<MyCollectionView, MyCollectionViewProtocol>
, DelegateProxyType
, MyCollectionViewProtocol {
public static func currentDelegate(for object: MyCollectionView) -> MyCollectionViewProtocol? {
return object.cvDelegate
}
public static func setCurrentDelegate(_ delegate: MyCollectionViewProtocol?, to object: MyCollectionView) {
object.cvDelegate = delegate
}
public weak private(set) var collectionView: MyCollectionView?
internal lazy var shouldScrollPublishSubject: PublishSubject<Void> = {
let localSubject = PublishSubject<Void>()
return localSubject
}()
public init(collectionView: ParentObject) {
self.collectionView = collectionView
super.init(parentObject: collectionView, delegateProxy: RxMyCollectionViewDelegateProxy.self)
}
// Register known implementations
public static func registerKnownImplementations() {
self.register { RxMyCollectionViewDelegateProxy(collectionView: $0) }
}
//implementation of MyCollectionViewProtocol
public func scrollViewShouldScrollToTop() {
shouldScrollPublishSubject.onNext(())
self._forwardToDelegate?.scrollViewShouldScrollToTop()
}
deinit {
shouldScrollPublishSubject.onCompleted()
}
}
Finally I declare my Reactive extension for MyCollectionView as
extension Reactive where Base: MyCollectionView {
public var delegate: DelegateProxy<MyCollectionView, MyCollectionViewProtocol> {
return RxMyCollectionViewDelegateProxy.proxy(for: base)
}
public var shouldScrollToTop: ControlEvent<Void> {
let source = RxMyCollectionViewDelegateProxy.proxy(for: base).shouldScrollPublishSubject
return ControlEvent(events: source)
}
}
Finally, I use it as
collectionView.rx.shouldScrollToTop.debug().subscribe(onNext: { (state) in
print("I should scroll to top")
}, onError: { (error) in
print("errored out")
}, onCompleted: {
print("completed")
}, onDisposed: {
print("Disposed")
}).disposed(by: disposeBag)
Question
Because none of the online tutorials(Raywenderlich)/courses (Udemy)/Books(Raywenderlich) explains how to convert the swift protocol to Rx style am confused as what I am doing is correct or wrong. The code works but even the worst designed code might work, hence I wanna be sure what am doing is correct or am messing something. I wrote the above code following the approach used in UIScrollView+Rx.swift and RxScrollViewDelegateProxy.swift
Though the code above works only for protocols without any return type example method I used above func scrollViewShouldScrollToTop() has no return type associated with it. I could not imagine how could I use the DelegateProxy above to convert the protocol methods with return types, like numberOfRowsInSection which has Int as return type.
I happened to look at the RxDataSource implementation and realized in order to convert cellForRowAtIndexPath RxDataSource constructor expects you to pass the block as a init parameter and executes it whenever tableView calls cellForRowAtIndexPath in its proxyDelegate.
Now I could do the same thing if thats the only way out. Need to know is that how am supposed to code it or can I modify ProxyDelegate implementation above to convert the protocol method with return types.
Ugly 1
protocol Persisting {
func persist()
}
extension Persisting {
func persist() { print("persisting") }
}
protocol Service {
func get()
func persistIfAble() // If I remove this, "Not able to persist" gets printed twice
}
extension Service {
func get() {
persistIfAble()
}
}
extension Service {
func persistIfAble() {
print("Not able to persist")
}
}
extension Service where Self: Persisting {
func persistIfAble() {
persist()
}
}
struct OnlyService: Service {}
struct Both: Service, Persisting {}
let both = Both()
both.get()
let onlyService = OnlyService()
onlyService.get()
print("Can now directly call `persistIfAble` which is not wanted")
onlyService.persistIfAble() // DONT WANT THIS TO BE POSSIBLE
This solution would be elegant if I could remove func persistIfAble() from protocol declaration. Because I do not want it to be exposed. However, what is really interesting is that if I remove it, then the behavior changes, then the implementation inside extension Service where Self: Persisting never gets called.
Ugly 2
protocol Persisting {
func persist()
}
extension Persisting {
func persist() { print("persisting") }
}
protocol Service {
func get()
}
extension Service {
func get() {
// Ugly solution, I do not want to cast, `Service` should not have to know about `Persisting`
if let persisting = self as? Persisting {
persisting.persist()
} else {
print("not able to persist")
}
}
}
extension Service where Self: Persisting {
func persistIfAble() {
persist()
}
}
struct OnlyService: Service {}
struct Both: Service, Persisting {}
let both = Both()
both.get()
let onlyService = OnlyService()
onlyService.get()
The code in both ugly solutions is of course an extremely simplified version of my actual scenario, where I really do not want to perform casts, because it makes the code so much more difficult to read. Even if I would change if let to guard let.
Ugly 3 (ugliest?)
protocol Persisting {
func persist()
}
extension Persisting {
func persist() { print("persisting") }
}
protocol Service {
func get()
func persistIfAble(allowed: Bool)
}
extension Service {
func get() {
persistIfAble(allowed: true)
}
}
extension Service {
func persistIfAble(allowed: Bool = false) {
guard allowed else { print("KILL APP"); return }
print("Not able to persist")
}
}
extension Service where Self: Persisting {
func persistIfAble(allowed: Bool = false) {
guard allowed else { print("BREAKING RULES"); return }
persist()
}
}
struct OnlyService: Service {}
struct Both: Service, Persisting {}
let both = Both()
both.get()
let onlyService = OnlyService()
onlyService.get()
print("Can now directly call `persistIfAble` which is not wanted")
// DONT WANT THIS TO BE POSSIBLE
onlyService.persistIfAble() // prints: "KILL APP"
What am I missing?
Where is the beautiful solution?
I wonder if maybe what you really want is to use composition of actual objects, not just interfaces (and some default implementations). Consider this: Both Persisting and Service as you've defined them really need to be implemented in concrete classes or structs, so that they can contain context about where they are accessing their data. So I'd imagine that you could skip the protocol extensions, leave the real "guts" up to concrete implementations of those protocols, and then something like your Both would be implemented like this:
struct Both: Persisting, Service {
let persisting: Persisting
let service: Service
// a default init lets you pass in concrete implementations of both of those things
func persist() {
persisting.persist()
}
func get() {
service.get()
persist()
}
This obviously doesn't give you the automatic sort of "mix-in" effect that it looks like you're trying to achieve, but OTOH it's pretty clear to understand.
How about removing one level of indirection (i.e. 'persistIfAble')?
protocol Persisting {
func persist()
}
extension Persisting {
func persist() {
print("persisting")
}
}
protocol Service {
func get()
}
extension Service where Self: Persisting {
func get() {
persist()
}
}
extension Service {
func get() {
print("Not able to persist")
}
}
It doesn't seem like I can cast a generic type to another? Swift is throwing DynamicCastClassException.
Basically here is the problem:
// T is defined as T: NSObject
let oebj1 = NetworkResponse<User>()
let oebj2 = oebj1 as NetworkResponse<NSObject>
Here is why I need to do this casting
class BaseViewController: UIViewController {
// Not allowed to make a generic viewController and therefore have to cast the generic down to NSObject
func fetchData(completion: (NetworkResponse<NSObject>)->()) {
fatalError("You have to implement fetchData method")
}
}
class UsersViewController: BaseViewController {
override func fetchData(completion: (NetworkResponse<NSObject>)->()) {
userNetworkManager.fetchUsers { networkUSerResponse in
completion(networkUSerResponse as NetworkResponse<NSObject>)
}
}
}
class UserNetworkManager {
func fetchUsers(completion: (NetworkResponse<User>)->()) {
// Do stuff
}
}
In general, there doesn't seem to be a way to do this. The basic problem is that NetworkResponse<NSObject> and NetworkResponse<User> are essentially completely unrelated types that happen to have identical functionality and similar naming.
In this specific case, it really isn't necessary since you're throwing away the known Userness of the result anyway, meaning that if you really want to treat it as a User later you'll have to do a conditional cast back. Just remove the generic from NetworkResponse and it will all work as expected. The major drawback is that within UserVC.fetchData you won't have access to the returned User result without a (conditional) cast.
The alternative solution would be to separate out whatever additional information is in NetworkResponse from the payload type (User/NSObject) using a wrapper of some sort (assuming there's significant sideband data there). That way you could pass the NetworkResponse to super without mutilation and down-cast the payload object as needed.
Something like this:
class User : NSObject {
}
class Transaction {
let request:NSURLRequest?
let response:NSURLResponse?
let data:NSData?
}
class Response<T:NSObject> {
let transaction:Transaction
let payload:T
init(transaction:Transaction, payload:T) {
self.transaction = transaction
self.payload = payload
}
}
class UserNetworkManager {
func fetchUsers(completion: (Response<User>) -> ()) {
completion(Response(transaction:Transaction(), payload:User()))
}
}
let userNetworkManager = UserNetworkManager();
class BaseVC {
func fetchData(completion: (Response<NSObject>) -> ()) {
fatalError("Gotta implement fetchData")
}
}
class UserVC : BaseVC {
override func fetchData(completion: (Response<NSObject>) -> ()) {
userNetworkManager.fetchUsers { response -> () in
completion(Response(transaction: response.transaction, payload: response.payload))
}
}
}
Although at that point, you're probably better off just separating the transaction information and payload information into separate arguments to the callback.