Search code examples
swiftgenericstestingprotocols

Protocol with associatedtype conformance with generic gives compiler error


I'm trying to make a DB mock to test some UI implementations, but compiler keeps giving me the following error: Type 'DBClientMock<T>' does not conform to protocol 'DBClient'

This is my code...

protocol DBClient {
    associatedtype Model
    func get(id: UUID, completion: @escaping ((Model?, Error?)) -> Void)
    func insert(_ model: Model, completion: @escaping (Error?) -> Void)
    func delete(_ model: Model, completion: @escaping (Error?) -> Void)
}

final class DBClientMock<T>: DBClient where T: Identifiable, T: Equatable {
    
    typealias Model = T
    
    private let queue: DispatchQueue
    private var modelDB = [T]()
    
    enum Error: Swift.Error {
        case notFound
    }
    
    init(queue: DispatchQueue = DispatchQueue.global()) {
        self.queue = queue
    }
    
    private func getModel(id: UUID) -> T? {
        let results = modelDB.filter({ $0.id as? UUID == id })
        guard results.count > 0 else { return nil }
        return results[0]
    }
    
    // Extension
    func get(id: UUID, completion: @escaping ((T?, Error?)) -> Void) {
        let record = getModel(id: id)
        queue.asyncAfter(deadline: .now() + .milliseconds(1500), execute: {
            if let model = record {
                completion((model, nil))
            } else {
                completion((nil, Error.notFound))
            }
        })
    }
    
    func insert(_ model: T, completion: @escaping (Error?) -> Void) {
        modelDB.append(model)
        queue.asyncAfter(deadline: .now() + .milliseconds(1000), execute: {
            completion(nil)
        })
    }
    
    func delete(_ model: T, completion: @escaping (Error?) -> Void) {
        modelDB.removeAll(where: { $0 == model })
        queue.asyncAfter(deadline: .now() + .milliseconds(800), execute: {
            completion(nil)
        })
    }
}

XCode: Version 12.4 (12D4e) Swift: 5.0

What am I doing wrong? Do I have to be more explicit with the generic type in some way? I tried replacing T with Model but had the same result.

Thanks for your help!


Solution

  • It doesn't conform because you declared another Error type inside the class, so everywhere where you use Error in the required methods, it uses DBClientMock.Error instead of the protocol-required Swift.Error.

    Either rename DBClientMock.Error to something else, or change the Error in methods to Swift.Error, like below:

    // Extension
    func get(id: UUID, completion: @escaping (T?, Swift.Error?) -> Void) {
       //...
    }
    
    func insert(_ model: T, completion: @escaping (Swift.Error?) -> Void) {
       //...
    }
    
    func delete(_ model: T, completion: @escaping (Swift.Error?) -> Void) {
       //...
    }