New feature to allow dependency injection without changing source code for testing

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.

Could you provide a small example?

Terms of Service

Privacy Policy

Cookie Policy