Group Group Group Group Group Group Group Group Group

Resolver for iOS Dependency Injection: Getting Started | raywenderlich.com

Learn how to use Resolver to implement dependency injection in your SwiftUI iOS apps to achieve easily readable and maintainable codebases.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/22203552-resolver-for-ios-dependency-injection-getting-started

Nice article. But a bit shocked to see it, actually, as I didn’t know that many people were using Resolver.

I’d probably handle the mock data testing a bit differently, as setting the defaultScope to .application is really changing the nature of the dependency graph and as such the behavior of the entire application. In other words, you’re now testing the new behavior and not the actual injection behavior you’d see when running the app.

You could get the same effect in your example by doing…

Resolver.mock.register { MockNetworkService() }
  .implements(NetworkServiceProtocol.self)
  .scope(.application)

Which would ensure the same mock service is used and persists for your asset class, but without changing the behavior of any other dependencies that may exist.

Again, nice article.

Michael Long, author of Resolver

Great article, thank you :slight_smile: Right on time as well, as I was planning to write my own DI framework. Resolver will save me some time, it seems.

I was impressed you included a section on testing, I find many tutorials online skip this part. That said, I am having some issues with the tests. I noticed you were testing async methods without expectations. That way the tests will pass even if the assertions fail - you can test that by changing the count assertion to check for, say, 200 elements. To fix that I modified the code to wait until the async methods complete before passing the tests, like so:

  func testFetchAssetsSuccessfully() {
    let asset = mockAsset()
    networkService.result = .success(assetList())
    let expectation = expectation(description: #function)

    sut?.fetchAssets { assetList, error in
      XCTAssertEqual(assetList?.data.count, 1)
      XCTAssertEqual(assetList?.data.first, asset)
      XCTAssertNil(error)
      expectation.fulfill()
    }

    wait(for: [expectation], timeout: 5)
  }

  func testFetchAssetsFailure() {
    let networkError = AppError.network(description: "Something went wrong!")
    networkService.result = .failure(networkError)
    let expectation = expectation(description: #function)

    sut?.fetchAssets { assetList, error in
      XCTAssertEqual(networkError, error)
      XCTAssertNil(assetList)
      expectation.fulfill()
    }
    wait(for: [expectation], timeout: 5)
  }

The first test passes, but the second fails - it seems like fetchAssets is returning real data instead of the mock result. Am I missing something?

Great article, thank you !
I just wanted to add the precision that there is an InjectedObject annotation for ObservedObject which needs to be injected.

@dchakarov Thanks for the tip ! For your issue I think that Resolver.registerMockServices() must be called before instantiating the AssetService in the setUp() function. Otherwise MockNetworkService is not yet registered when AssetService asks for a NetworkServiceProtocol.
Even when using MocNetworkService we still need to use expectations since AssetService is calling DispatchQueue.main.async on completion.

If for one protocol like NetworkServiceProtocol, we have different implementation like NetworkServiceA and NetworkServiceB. And in different use case, we want to inject different ones. How does Resolver framework fullfill this?