2

I've been searching for so long about how to design testable classes in Swift but every information site just describe basic stuff like how to test, how to assert, why is testing good...

My current design (which I'm not so sure is the best) includes a singleton ApiClient class that gets initialized on the AppDelegate. Something like this:

// Inside AppDelegate#application:didFinishLaunchingWithOptions
ApiClientFactory.make();

// Inside ApiClientFactory
static func make() {
    ApiClient.initialize(
        userProvider: UserProvider(UserLocalRepo(), UserRemoteRepo()),
        booksProvider: BooksProvider(BookLocalRepo(), BookRemoteRepo()),
        ...
    )
}

The idea was to inject into the *Providers classes, mock repos when testing. But due to the way VCs are instantiated I'm not sure this is a good design at all, and I'm not sure where would I inject those mock repo, as it seems AppDelegate#application:didFinishLaunchingWithOptions still runs on tests (which means ApiClient will be initialized with the actual classes, not the mock ones)

4

I think you're running into a common pain point while testing and one of the reasons I dislike singleton implementations. Injecting these repository instances is a useful pattern allowing you to substitute test doubles or change the configuration of the API client in the future without needing to modify its implementation. However by implementing it as a singleton you lose much of this flexibility, as you seem to have noticed. Not only will you have to deal with your application:didFinishLaunchingWithOptions: method being called but you also need to be careful not to allow shared state in this singleton to leak from one test into subsequent tests.

One option would be to allow those repositories to be modified after initialization so you can later replace them with test doubles. Unfortunately that also allows for a mutable configuration which you may not want to allow in your app.

My preference would be to not use a singleton at all and replace this ApiClient class with an instance of the class injected into whichever components need to use it. You can then create independent instances isolated to each test as necessary. In your application perhaps you will still only have a single ApiClient instance but that doesn't mean it needs to be accessed via class methods or enforced as a constraint of its implementation.

  • I like this approach. Supposing I decide to go with it, how would I inject the ApiClient into a VC, if every VC are instantiated via the storyboard? My question is focused on injecting Mock repositories to said ApiClient when it is injected to the VC in tests. – Christopher Francisco Jun 29 '15 at 20:27
  • So far I've been handling dependency injection in Swift manually and not relying on Storyboards to build my view controllers so I may not have the best answer. I like the way August handled this in objective-c here: blog.carbonfive.com/2014/07/09/… and would look to follow that in Swift as well. Exactly how you handle injection will depend on if you think it is worthwhile to build a DI container class or take some other approach. – Jonah Jun 29 '15 at 21:15
2

Singletons are a bad idea in the Apple API. Objective-C does not support singletons, instead, you create one instance that blocks the formation of any others, a crude work around IMHO, that doesn't always work. I don't think is any better in Swift and given it has to support the Objc API, I doubt it.

The preferred method to ensure a single instance is to simply place the object as a property of the app delegate. Since there is only one app and it only has one delegate and the app delegate is always alive, you get all the convenience of a singleton with none of the hassle. The "singleton" is also accessible from anywhere in the app as well since the app delegate is always accessible.

Then unit testing would proceed normally.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.