deinit called but the uint testing not finished - swift

I'm trying to test my coordinator flow but the child coordinator deinit called before the unit test case finished
My coordinator class
public final class AppCoordinator: Coordinator {
public var childCoordinators: [Coordinator] = []
public var navigationController: UINavigationController
var window: UIWindow?
public init(window: UIWindow?) {
self.window = window
let secController = SecController()
self.navigationController = UINavigationController(rootViewController: secController)
secController.delegate = self
}
public func start() {
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
}
}
extension AppCoordinator: SecControllerDelegate, SignInControllerDelegate {
public func removeSingIn() {
self.childCoordinators.removeFirst()
}
public func showSignIn() {
let signInCoordinator = SignInCoordinator(navigationController: self.navigationController)
signInCoordinator.delegate = self
self.childCoordinators.append(signInCoordinator)
signInCoordinator.start()
}
}
Unit test class
class AppCoordinatorTests: XCTestCase {
var coordinator: AppCoordinator!
override func setUp() {
super.setUp()
coordinator = AppCoordinator(window: UIWindow())
}
override func tearDown() {
coordinator = nil
super.tearDown()
}
func testStartMethod() {
coordinator.start()
XCTAssertNotNil(coordinator.window?.rootViewController)
}
func testShowSignIn() {
coordinator.showSignIn()
XCTAssertFalse(coordinator.childCoordinators.isEmpty)
XCTAssertTrue(coordinator.navigationController.visibleViewController is SignInController)
}
}
when try to test testShowSignIn always failed because of the deinit call removeSingIn function
public class SignInController: UIViewController {
public weak var delegate: SignInControllerDelegate?
public init() {
super.init(nibName: nil, bundle: nil)
}
deinit {
self.delegate?.removeSingIn()
}
}

Let's review the steps:
testShowSignIn calls coordinator.showSignIn(), where coordinator is an AppCoordinator.
showSignIn() instantiates a SignInCoordinator, and sets its delegate to the AppCoordinator instance.
Now we reach the important part:
We reach the end of showSignIn(). The SignInCoordinator goes out of scope, so Swift destroys it.
Nothing maintains a reference to the SignInCoordinator. But you want to test the interaction between the AppCoordinator and the SignInCoordinator. The code is fighting you, because AppCoordinator decides to create and destroy the SignInCoordinator on its own.
You can test it by changing the design. You have a couple of options.
Option 1: Change AppCoordinator to have a lazy computed property that returns the SignInCoordinator. This can work if you're okay with that design. Then the SignInCoordinator will continue to live, so that the test can query it. This improves the testability of AppCoordinator by exposing the SignInCoordinator.
Option 2: Have the test create a SignInCoordinator and pass it in as an argument to showSignIn(). Then the SignInCoordinator lifecycle will be managed completely outside of AppCoordinator.

Related

Swift, iOS: How to get Polymorphic and Decouple Coordinator from concrete type

I have these protocols:
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func mainViewController() -> UIViewController
}
And I created a MainCoordinator that conforms to this protocol and I pass a factory that allows me to decouple the coordinator from creating and capturing a concrete type so it can be polymorphic and can be used with more implementations of UIViewController either as rootViewControllers and mainMenuViewController as shown below:
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
}
start() {
guard let mainVC = factory.mainViewController() as? MainViewController, let rootViewController = rootViewController as? UINavigationViewController else { return }
mainVC.delegate = self
rootViewController.push(mainVC, animated: true)
}
As you can see, although I've created the coordinator to accept any subclass of UIViewController it has been coupled in the start function to the concrete implementation of UIViewController: MainViewController.
So my question is how to decouple it from MainViewController and have it more polymorphic?
You can pass coordinator as a parameter type in factory function and set delegate directly in factory function while creating controller instance. That way you wouldn’t have to expose controller type explicitly out of factory classes.
I came up with below approach.
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?
}
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
self.factory = factory
}
func start() {
guard let controller = factory.getViewController(delegateType: .MainCoordinator, delegateObject: self),let rootViewController = rootViewController as? UINavigationViewController else {
return
}
rootViewController.push(mainVC, animated: true)
}
}
extension MainCoordinator:DelegateCaller{
func printHello() {
print("helloo")
}
}
enum CoordinatoreTypes{
case MainCoordinator
case none
}
class Factory:UIViewControllerFactory{
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?{
switch delegateType{
case .MainCoordinator:
let controller = MainViewController()
controller.delegate = delegateObject as? MainCoordinator
return controller
case .none:
break
}
return nil
}
}
class MainViewController:UIViewController{
weak var delegate:DelegateCaller?
}
protocol DelegateCaller:AnyObject{
func printHello()
}

RxSwift Subject not firing event when called

I have an app using MVP with the Coordinator pattern.
When a child coordinator sends and event, I would expect my AppCoordinator to recursively call a method that selects the next coordinator based on some SessionState.
The basic flow of the app is as follows -
AppCoordinator
start() invokes coordinateToRoot with an initial state
Subscribes to showStartScene() which starts the child coordinator
StartCoordinator
start() creates MVP module which is now visible to the user
MVP module invokes AuthSvc which makes async call to iDP and confirms auth state
On completion of this task, publishes an event which is picked up by the subscription in the AppCoordinator's coordinateToRoot method and the cycle repeats using the appropriate coordinator for the view state.
The issue however is that on the publish of that event, nothing is happening. start() is not showing it received the event and coordinateToRoot is not called again.
I have created the most basic version I can below to demonstrate this. I have also hardcoded showStartScene to return .signedIn rather than a look up of the auth state.
In the below example, I would expect once the view is loaded, presenter.signal should immediately emit an event that causes a print statement to show.
SessionState
enum SessionState: String {
case unknown, signedIn, signedOut
}
AppCoordinator
final class AppCoordinator: BaseCoordinator<Void> {
private let window: UIWindow
init(window: UIWindow) {
self.window = window
}
override func start() -> Observable<Void> {
coordinateToRoot(basedOn: .unknown)
return .never()
}
/// Recursive method that will restart a child coordinator after completion.
/// Based on:
/// https://github.com/uptechteam/Coordinator-MVVM-Rx-Example/issues/3
private func coordinateToRoot(basedOn state: SessionState) {
switch state {
case .unknown:
return showStartScene()
.subscribe(onNext: { [unowned self] state in
self.window.rootViewController = nil
self.coordinateToRoot(basedOn: state)
})
.disposed(by: disposeBag)
case .signedIn:
print("I am signed in")
case .signedOut:
print("I am signed out")
}
}
private func showStartScene() -> Observable<SessionState> {
let coordinator = StartCoordinator(window: window)
return coordinate(to: coordinator).map { return .signedIn }
}
}
StartCoordinator
final class StartCoordinator: BaseCoordinator<Void> {
private(set) var window: UIWindow
init(window: UIWindow) {
self.window = window
}
override func start() -> Observable<CoordinationResult> {
let viewController = StartViewController()
let presenter = StartPresenter(view: viewController)
viewController.configurePresenter(as: presenter)
window.rootViewController = viewController
window.makeKeyAndVisible()
return presenter.signal
}
}
Start MVP Module
protocol StartViewInterface: class {
func configurePresenter(as presenter: StartPresentation)
}
protocol StartPresentation: class {
var viewIsReady: PublishSubject<Void> { get }
var signal: PublishSubject<Void> { get }
}
// MARK:- StartPresenter
final class StartPresenter {
// Input
let viewIsReady = PublishSubject<Void>()
// Output
let signal = PublishSubject<Void>()
weak private var view: StartViewInterface?
private lazy var disposeBag = DisposeBag()
init(view: StartViewInterface?) {
self.view = view
viewIsReady.bind(to: signal).disposed(by: disposeBag)
}
}
extension StartPresenter: StartPresentation { }
// MARK:- StartViewController
final class StartViewController: UIViewController {
private var presenter: StartPresentation?
override func viewDidLoad() {
super.viewDidLoad()
if let presenter = presenter {
presenter.viewIsReady.onNext(())
}
}
}
extension StartViewController: StartViewInterface {
func configurePresenter(as presenter: StartPresentation) {
self.presenter = presenter
}
}
Interestingly if I do something like this in StartCoordinator the process does work, it has however not what I am trying to achieve.
override func start() -> Observable<CoordinationResult> {
let viewController = StartViewController()
let presenter = StartPresenter(view: viewController)
viewController.configurePresenter(as: presenter)
window.rootViewController = viewController
window.makeKeyAndVisible()
let subject = PublishSubject<Void>()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
subject.onNext(())
}
return subject
}
For reference my BaseCoordinator looks like -
/// Base abstract coordinator generic over the return type of the `start` method.
class BaseCoordinator<ResultType>: CoordinatorType {
/// Typealias which allows to access a ResultType of the Coordainator by `CoordinatorName.CoordinationResult`.
typealias CoordinationResult = ResultType
/// Utility `DisposeBag` used by the subclasses.
let disposeBag = DisposeBag()
/// Unique identifier.
internal let identifier = UUID()
/// 1. Stores coordinator in a dictionary of child coordinators.
/// 2. Calls method `start()` on that coordinator.
/// 3. On the `onNext:` of returning observable of method `start()` removes coordinator from the dictionary.
///
/// - Parameter coordinator: Coordinator to start.
/// - Returns: Result of `start()` method.
func coordinate<T: CoordinatorType, U>(to coordinator: T) -> Observable<U> where U == T.CoordinationResult {
store(coordinator: coordinator)
return coordinator.start()
.do(onNext: { [weak self] _ in self?.free(coordinator: coordinator) })
}
/// Starts job of the coordinator.
///
/// - Returns: Result of coordinator job.
func start() -> Observable<ResultType> {
fatalError(message: "Start method should be implemented.")
}
/// Dictionary of the child coordinators. Every child coordinator should be added
/// to that dictionary in order to keep it in memory.
/// Key is an `identifier` of the child coordinator and value is the coordinator itself.
/// Value type is `Any` because Swift doesn't allow to store generic types in the array.
private(set) var childCoordinators: [UUID: Any] = [:]
/// Stores coordinator to the `childCoordinators` dictionary.
///
/// - Parameter coordinator: Child coordinator to store.
private func store<T: CoordinatorType>(coordinator: T) {
childCoordinators[coordinator.identifier] = coordinator
}
/// Release coordinator from the `childCoordinators` dictionary.
///
/// - Parameter coordinator: Coordinator to release.
private func free<T: CoordinatorType>(coordinator: T) {
childCoordinators[coordinator.identifier] = nil
}
}
EDIT
I added some debug operators and I can see the order appears off for the next event and the subscription
2019-11-08 10:26:19.289: StartPresenter -> subscribed
2019-11-08 10:26:19.340: StartPresenter -> Event next(())
2019-11-08 10:26:19.350: coordinateToRoot -> subscribed
Why is coordinateToRoot subscribing after StartPresenter is created?
coordinateToRoot is not connected to the lifecycle of the Observable returned by AppCoordinator.start(_:). This means that there is no guarantee to the order in which coordinateToRoot and StartPresenter are subscribed to.
To guarantee the order, I think you can use the do operator and pass a closure for the onSubscribe argument. This onSubscribe closure will run before the subscribing to the underlying observable.
Here is the change I think you could make:
final class AppCoordinator: BaseCoordinator<Void> {
override func start() -> Observable<Void> {
return Observable<Void>.never().do(onSubscribe: { [weak self] _ in
self?.coordinateToRoot(basedOn: .unknown)
})
}
}

How to unit test that private method is called in Swift

I have a ViewController class that presents a series of two choice popup views. Each two choice popup view is different.
Popup1 - Choice1 -> Choice1Popup
Popup1 - Choice2 -> Choice2Popup
I intend the method to present Popup1 to be public, but I want the other methods that present Choice1Popup and Choice2Popup to be private.
If I decide I need to test Choice1Popup and Choice2Popup then I may have to make them internal instead of private, but they are unlikely to ever be used from any other place.
I want to write a unit test that tests when the button for Choice1 is touched that the method that presents Choice1Popup is called. I've used a protocol with method type variables to allow a Mock to inject the Mock versions of the popup presenters. I'm not feeling 100% comfortable about my approach so I wanted to get input as to whether or not there is a better way.
An aside I'm feeling conflicted about internal versus private. It would be nice to be able to test my private methods but I don't want them to be able to be called from anywhere but a unit test and making them internal exposes them.
Here is the code and a single Unit test is at the bottom:
// protocol to be used by both UserChoices class and UserChoicesMock for method injection
protocol UserChoicesPrivateUnitTesting {
static var choice1Method:(UIViewController) -> Void { get set }
static var choice2Method:(UIViewController) -> Void { get set }
}
// this popup that will be presented with a public method
public class ChoiceViewController:UIViewController {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var subjectLabel: UILabel!
#IBOutlet weak var choice1Button: UIButton!
#IBOutlet weak var choice2Button: UIButton!
var choice1Action:(() -> Void)?
var choice2Action:(() -> Void)?
// ...
}
public class UserChoices: UIViewController, UserChoicesPrivateUnitTesting {
static var choice1Method: (UIViewController) -> Void = choice1
static var choice2Method: (UIViewController) -> Void = choice2
private static func choice1(onTopViewController: UIViewController) {
//present choice1Popup
}
private static func choice2(onTopViewController: UIViewController) {
//present choice2Popup
}
public static func presentChoiceViewController(onTopViewController: UIViewController, ChoiceViewController: ChoiceViewController = ChoiceViewController.instantiateFromAppStoryBoard(appStoryBoard: .MenuStoryboard)) {
let isCustomAnimated = true
// ChoiceViewController.transitioningDelegate = transitionDelegate
ChoiceViewController.choice1Action = { [weak onTopViewController]() in
guard let weakSelf = onTopViewController else {
return
}
weakSelf.dismiss(animated: false, completion: nil)
UserChoices.choice1Method(onTopViewController!)
}
ChoiceViewController.choice2Action = { [weak onTopViewController]() in
guard let weakSelf = onTopViewController else {
return
}
weakSelf.dismiss(animated: false, completion: nil)
UserChoices.choice2Method(onTopViewController!)
}
onTopViewController.present(ChoiceViewController, animated: isCustomAnimated, completion: nil)
}
}
import XCTest
#testable import ChoiceModule
public class UserChoicesMock:UserChoicesPrivateUnitTesting {
static public var choice1Method: (UIViewController) -> Void = choice1
static public var choice2Method: (UIViewController) -> Void = choice2
static var choice1MethodCalled = false
static var choice2MethodCalled = false
static func choice1(onTopViewController: UIViewController) {
choice1MethodCalled = true
}
static func choice2(onTopViewController: UIViewController) {
choice2MethodCalled = true
}
}
class UserChoicesTests: XCTestCase {
func testChoice1CallsPrivateChoice1Method() {
// This is an example of a functional test case.
let vc = UIViewController()
let choiceViewController = ChoiceViewController.instantiateFromAppStoryBoard(appStoryBoard: .MenuStoryboard)
UserChoices.choice1Method = UserChoicesMock.choice1Method
UserChoices.presentChoiceViewController(onTopViewController: vc, ChoiceViewController: choiceViewController)
choiceViewController.choice1Button.sendActions(for: .touchUpInside)
if UserChoicesMock.choice1MethodCalled == false {
XCTFail("choice1Method not called")
}
}
}
Tests can't access anything declared private. They can access anything declared internal as long as the test code does #testable import.
When you get that queasy feeling, "But I shouldn't have to expose this," consider that your class actually has multiple interfaces. There's the "everything it does interface" and there's the "parts needed by production code interface." There are various things to consider about this:
Is there another type that is trying to get out?
Is there another protocol to express a subset of the interface? This could be used by the rest of production code.
Or maybe it's like a home theater amplifier where "controls you don't need that often" are hidden behind a panel. Don't sweat it.

Binding does not trigger event in ViewController using MVVM in Swift 4

I am trying to learn binding and understand the MVVM approach in Swift.
I was expecting the below example to work, essentially someEventHappened is called, this invokes the onEvent closure and my message is logged to the screen.
This does not happen however, nothing is printed and I am a little unsure as to why?
class ViewModal {
public var onEvent: (() -> Void)?
func someEventHappened() -> Void {
onEvent?()
}
}
class ViewController: UIViewController {
lazy var viewModel: ViewModal = {
let viewModal = ViewModal()
return viewModal
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = .purple
viewModel.someEventHappened()
viewModel.onEvent = {
print("something happened")
}
}
}
Just swap assigning onEvent and calling someEventHappened
viewModel.onEvent = {
print("something happened")
}
viewModel.someEventHappened()
this is because you're calling onEvent handler inside someEventHappened and in viewDidLoad you first had called someEventHappened and then assigned onEvent

What's the best way to watch the change of data in Cocoa

I have a singleton to store some global data for my macOS app, one of my ViewController keeps modifying data. I want to simultaneously show the changes in a View, which is related to another ViewController. what 's the best way to do this?
Global Data:
final class AppData {
static var logs: [LogData] = []
}
ViewController 1:
class FirstViewController: NSViewController {
AppData.logs.append(newLogData)
}
ViewController 2:
class SecondViewController: NSViewController {
// what's the best way to simultaneously watch the change of AppData.logs?
}
If your App is planned to be macOS only you can use a NSObjectController. This is definitively the easiest approach and you can do most of the configuration in Interface builder. It works internally with bindings. In case of an array you want to observe, you would use a NSArrayController.
One way is to use the notificationcenter
In viewcontroller2 add:
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.default.addObserver(self,
selector: #selector(view1DidChange),
name: "view1DidChange",
object: nil
)
}
#objc private func view1DidChange(_ notification: Notification) {
// Do something
}
In viewcontroller1 add
notificationCenter.default.post(name: "view1DidChange", object: self)
This can be repeated in every class, that should listen.
Here i am sharing the Delegate & Protocol approach to achieve this functionality.
final class AppData {
static var logs: [LogData] = []
}
protocol FirstViewControllerDelegate {
func ViewControllerDelegate(appData:[LogData])
}
class FirstViewController: NSViewController {
var delegate:FirstViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
AppData.logs.append(newLogData)
self. delegate?.ViewControllerDelegate(appData: AppData.logs)
}
}
class SecondViewController: NSViewController,FirstViewControllerDelegate {
var firstViewController:FirstViewController = FirstViewController()
override func viewDidLoad() {
self.firstViewController.delegate = self
}
func ViewControllerDelegate(appData:[LogData]){
//Do Update the UI
}
}