hmlongco / Factory

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

Issues with SPM packages and Container static shared instance #179

Open alaw-viator opened 5 months ago

alaw-viator commented 5 months ago

We've been using Factory for a while in our project trouble free. However, we've just begun to run into issues whereby all of our mocked dependencies setup from unit tests are being ignored.

This issue started when we made the final migration to remove CocoaPods in favour of SPM. Previously we were utilising development CocoaPods for all our modules, whereas these are now local SPM packages. A trimmed down example of one of our SPM module packages looks like this:

let package = Package(
    name: "ModuleA",
    defaultLocalization: "en",
    platforms: [
        .iOS(.v15),
    ],
    products: [
        .library(
            name: "ModuleA",
            targets: ["ModuleA"]
        ),
        .library(
            name: "ModuleAMocks",
            targets: ["ModuleAMocks"]
        ),
    ],
    dependencies: [
        .package(
            url: "https://github.com/hmlongco/Factory.git",
            exact: "2.2.0"
        )
    ],
    targets: [
        .target(
            name: "ModuleA",
            dependencies: [
                .product(name: "Factory", package: "Factory")
            ],
            path: "Framework/Sources",
            plugins: [
            ]
        ),
        .target(
            name: "ModuleAMocks",
            dependencies: [
                .byName(name: "ModuleA"),
            ],
            path: "Tests/Sources/Mocks",
            plugins: [
            ]
        ),
        .testTarget(
            name: "ModuleATests",
            dependencies: [
                .byName(name: "ModuleA"),
                .byName(name: "ModuleAMocks")
            ],
            path: "Tests/Sources",
            exclude: [
                "Mocks",
            ],
            plugins: [
            ]
        ),
    ]
)

And the DI container defined in the ModuleA target:

public final class ModuleAContainer: SharedContainer {
    public static var shared = ModuleAContainer()
    public var manager = ContainerManager()
}

And then a mock extension on the container defined in the ModuleAMocks target

public extension ModuleA {
    static func setUpMocks() {
      // shared.depdency.register { mockedDependency }
      // ...
    }

Each unit test class then calls the required ModuleA.setUpMocks() etc in the setup.

It's worth also mentioning that each modules suite of unit tests run fine when run in isolation, i.e. directly from the SPM package. However, the issues are back in the main project, both on the test plan comprising the suite of units tests which are part of the main projects test target and the test plan comprising of all of the SPM modules unit tests.

Despite the tests setting all the mocked dependencies, when the projects main code resolves those dependencies during a test execution, a new instance of the container is created, not the statically allocated one created by the test fixture calling setUpMocks().

Given that static variables are scoped to a module in Swift (by design) and SPM packages are compiled separately to one another, I'm struggling to see how it's possible to use Factory in the context of a modularised project which utilises SPM for those modules because having a statically allocated shared container is a fundamental requirement of a container.

Has anyone else encountered similar issues and if so how did you approach this problem? Thanks!

kuchmiyalex commented 5 months ago

I believe we're experience a similar outcome while using Factory as an SPM dependency across multiple modules.

While running unit tests in main project the container is being initialized more than once and instead of using mock it uses real implementation from the ModuleA.

I've created a demo project including two SPM modules(which runs fine in isolation), but then linked together into main target produce multiple container recreation. If ModuleBMocks is removed from ProjectTests's linked frameworks then unit tests working properly in the main target(testBuilderExample). Here are two projects, one containing ModuleBMocks dependency and another doesn't. Please suggest any workaround here, thanks!

ProjectSuccess.zip

ProjectFail.zip

emans commented 5 months ago

I am also having a similar situation, could you please help with this?