hmlongco / Factory

A new approach to Container-Based Dependency Injection for Swift and SwiftUI.
MIT License
1.83k stars 115 forks source link

UITesting Question - Mock Response Scaling Concern #196

Closed adamsousa closed 6 months ago

adamsousa commented 6 months ago

Hi, recently have started using Factory and I have a question revolving around UITesting and mocking dependancies. I've read through the testing documentation and looked through issues submitted here, but none have really answered my question.

My question pertains to mocking factory dependencies but with many variants of that dependancy. For example in my UI tests, I mock a network request response, but each test case tests a different enum value:

class MockNetworkRequest {
    private let stubbedResponse: ResponseEnum

    init(stubbedResponse: ResponseEnum) {
        self.stubbedResponse = stubbedResponse
    }

    func getResponse() -> ResponseEnum {
        stubbedResponse
    }
}

enum ResponseEnum {
    case type1
    case type2
    case type3
    case type4
    case type5
}
func testResponseType1() {
        let app = XCUIApplication()
        app.launchArguments.append("type1") // passed parameter
        app.launch()

        app.staticTexts["Type 1"]
}

func testResponseType2() {
        let app = XCUIApplication()
        app.launchArguments.append("type2") // passed parameter
        app.launch()

        app.staticTexts["Type 2"]
}

... etc

My understand is in order to mock this using Factory, I would have to create a Container extension that conforms to AutoRegistering. From there I could do something like this:

extension Container: AutoRegistering {
    public func autoRegister() {
        if ProcessInfo().arguments.contains("type1") {
            networkModel.register { MockNetworkRequest(stubbedResponse: .type1) }
        } else if ProcessInfo().arguments.contains("type2") {
            networkModel.register { MockNetworkRequest(stubbedResponse: .type2) }
        } else if ProcessInfo().arguments.contains("type3") {
            networkModel.register { MockNetworkRequest(stubbedResponse: .type3) }
        } else if ProcessInfo().arguments.contains("type4") {
            networkModel.register { MockNetworkRequest(stubbedResponse: .type4) }
        } else if ProcessInfo().arguments.contains("type5") {
            networkModel.register { MockNetworkRequest(stubbedResponse: .type5) }
        }
    }
}

While this would work, this extension is already starting to become bloated with different responses. This won't scale well, as this extension will slowly become somewhat of a "monster class" as I add more tests and different mocked responses.

Ideally I would like these mock responses to be defined in my UITesting .swift files, that way their self contained. But I'm not able to do something like private extension Container: AutoRegistering as AutoRegistering is public. I've also found that a UI Testing Bundle does not have access to a Container created on the main application side, so my understand is the launchArguments are my only way to interface with it.

So I guess my real question is, is there a better way mock these objects? One that scales well? Maybe I'm just missing something here, appreciate your time!

hmlongco commented 6 months ago

This is why many people do unit tests on modules to test edge cases and then basically use UI testing for happy path smoke testing. Factory provides a certain level of support for such things, but if you're going to focus on UI tests for such things you're probably going to need to focus on using WireMock or some other external data source.

adamsousa commented 6 months ago

@hmlongco Makes sense, thanks for the response šŸ‘