BLACK FRIDAY SALE: Save 50% on all my Swift books and bundles! >>

Unit Testing with the Singleton

Forums > Swift

Hey guys.

I try to understand how to create dependency injection with the Singleton in order to test it. Due to its private init() {} you cant create an instance of it in your code.

Now, I have seen some techniques where people change private init() {} to init() {} and create a subclass of that Singletone with override init.

From what I understand, the very essence of Signleton is exactly to have its initialiser private.

Is this still a legit way to do so? Is there a way to create that dependency injection while keeping my Singleton's initialiser private?

Many thank for any help!

1      

You would be best off getting rid of the singletons and just injecting in the service to your view models. I've provided an example below with some networking, which is a common place where people use singletons. I have used Combine here, but you can just replace all that with closures if required. Basically, you create a Protocol that the Mock and Live must conform each of the required methods. You can then inject the an APIClientProtocol into the view model, with a default of live to make the code nicer. And then for testing you can inject in the MockAPIClient which will not hit the actual network.

import Combine
import Foundation

struct User: Decodable, Identifiable {
    let id: Int
    let name: String
}

protocol APIClientProtocol {
    func getPosts() -> AnyCancellable<[User], Error>
}

struct MockAPIClient: APIClientProtocol {
    func getPosts() -> AnyCancellable<[User], Error> {
        // mock test logic here
    }
}

struct APIClient: APIClientProtocol {
    func getPosts() -> AnyCancellable<[User], Error> {
        // live logic here
    }
}

class UsersViewModel: ObservableObject {
    private let apiClient: APIClientProtocol
    private var cancellables = Set<AnyCancellable>()

    @Published var users = [User]()

    init(apiClient: APIClientProtocol = APIClient()) {
        self.apiClient = apiClient
    }

    func getUsers() {
        apiClient.getPosts()
            .sink { completion in
                switch completion {
                case .finished: break
                case .failure(let error):
                    print(error.localizedDescription)
                } receivedValue: { [weak self] users in
                    self?.users = users
                }
                .store(in: &cancellables)
    }
}

// testing the ViewModel

let testViewModel = UsersViewModel(apiClient: MockAPIClient())
// ...

2      

Save 50% in my Black Friday sale.

SAVE 50% To celebrate Black Friday, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.