NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

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      

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

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.