Hi,

currently there is no clean way to implement dependency injection without modifying the code that should be tested. This is not ideal. How about a language feature that would allow rebinding types for a small context:

import Foundation

struct FileReader {
    
    func readInfo() throws -> Data {
        let infoURL = URL(fileURLWithPath: "/Info")
        return try Data(contentsOf: infoURL)
    }
    
}

import XCTest

class FileReaderTests: XCTestCase {
    
    func testReadInfo() {
        // This call would allow to rebind types with mocks and execute a closure with those rebound types
        Inject(Data: DataMock) {
            let info = try FileReader().readInfo()
            
            // asserts
        }
    }
    
}

struct DataMock {
    
    init(contentsOf url: URL, options: Data.ReadingOptions = []) {
        // do whatever you need to do to test your stuff
    }
    
}

So it looks like you're proposing Inject(TypeToReplace: ReplacementType, () -> Void) where the given closure is executed in a context where references to TypeToReplace are redirected to ReplacementType.

I have no idea how feasible that is to implement, but I'm not sure using the type name as a parameter label is the best approach. I would suggest something more like inject(replacing: Type1, with: Type2) {···}

So it looks like you're proposing Inject(TypeToReplace: ReplacementType, () -> Void) where the given closure is executed in a context where references to TypeToReplace are redirected to ReplacementType .

Yes exactly.

I have no idea how feasible that is to implement, but I'm not sure using the type name as a parameter label is the best approach. I would suggest something more like inject(replacing: Type1, with: Type2) {···}

I agree, my proposed syntax was just a quick way to show what I mean. Since it would be necessary to rebind multiple types in a single context the syntax would be more like inject(_ replacementList: [(replacing: Type1, with: Type2)]) {···}

I have seen this being done by providing a protocol with a single implementation (meaning the optimizer can handle it) but changing which implementation is compiled in upon whether or not one is testing (using a -Dflag or the like. I am not saying it is the best way to do it, but it should work ok and not require a new feature.

1 Like

Could you provide a small example?