hmlongco / Factory

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

UITests LaunchArguments Failing in Headless Mode #170

Closed michaelbutler1998 closed 3 months ago

michaelbutler1998 commented 7 months ago

I have been using Launch Arguments to modify if my user is logged in (authenticated launch argument) or logged out (unauthenticated launch argument) for testing, having followed this part of the docs https://hmlongco.github.io/Factory/documentation/factory/testing/#Xcode-UITesting. This works perfectly when running the tests within Xcode, with all tests passing.

When I switch to using Fastlane Tests or xcodebuild test in the terminal to run the test, these same tests fail. I assume this is due to the headless mode that these tests are run in.

Here's a reproduced example of what I've been trying. Hopefully this slimmed down example displays well enough what I'm trying to highlight, if you need more information let me know

UI Tests File:

import XCTest
final class UITestsV2: XCTestCase {
    func testMinimumReproduceableExample() throws {
        let app = XCUIApplication()
        app.launchArguments.append("authenticated")
        app.launch()
        XCTAssertTrue(app.buttons["New"].exists, "The 'New' button does not exist")
    }
}

Container+Extensions File:

extension Container: AutoRegistering {
    public func autoRegister() {
        if ProcessInfo().arguments.contains("unauthenticated") {
            let authTokenRepositoryMock = AuthTokenRepositoryMock()
            authTokenRepositoryMock.changeMockUserAuthenticated(newStatus: false)
            authTokenRepository.register { authTokenRepositoryMock }

        } else if ProcessInfo().arguments.contains("authenticated") {
            let authTokenRepositoryMock = AuthTokenRepositoryMock()
            authTokenRepositoryMock.changeMockUserAuthenticated(newStatus: true)
            authTokenRepository.register { authTokenRepositoryMock }
        }
    }
}

And here is the file which changes the authorisation status to determine if the user is logged in or out:

import Combine
import Factory
import Foundation

public class AuthTokenRepositoryMock: AuthTokenRepositoryProtocol {

    private var mockUserAuthenticatedStatus: Bool = true

    public init() {
    }

    public func changeMockUserAuthenticated(newStatus: Bool) {
        mockUserAuthenticatedStatus = newStatus
    }

    public func hasToken() -> Bool {
        return mockUserAuthenticatedStatus
    }
}
michaelbutler1998 commented 7 months ago

I have further refined this issue and perhaps have noticed that it was not what I first anticipated that is causing the issue.

It seems this line is what is causing my issues authTokenRepositoryMock.changeMockUserAuthenticated(newStatus: false). If instead of changing the authenticated status through a function, I create two Mocks: one for auth which hasToken returns true and one for unauth which hasToken returns false, this works fine.

I'm still unsure why, but perhaps this is not a Factory specific issue

extension Container: AutoRegistering {
    public func autoRegister() {
        if ProcessInfo().arguments.contains("unauthenticated") {
            let authTokenRepositoryMock = AuthTokenRepositoryMockUnAuthenticated()
            authTokenRepository.register { authTokenRepositoryMock }

        } else if ProcessInfo().arguments.contains("authenticated") {
            let authTokenRepositoryMock = AuthTokenRepositoryMockAuthenticated()
            authTokenRepository.register { authTokenRepositoryMock }
        }
    }
}