[iOS、Swift] ユニットテストの時に、任意のタイミングでViewDidLoad()、ViewWill(Did)Appear()、ViewWill(Did)Disappear()を呼び出す方法

忘れがちなので備忘も兼ねてまとめておきます。

# 事前準備
以下のようなView Controller(Initial View Controller)を作成します。

```swift:HogeViewController.swift

import UIKit

class HogeViewController: UIViewController {
    
    // ViewDidLoad()用フラグ
    var isViewDidLoadCalled = false
    
    // ViewWill(Did)Appear()用フラグ
    var isViewWillAppearCalled = false
    var isViewDidAppearCalled = false
    
    // ViewWill(Did)Disappear()用フラグ
    var isViewWillDisappearCalled = false
    var isViewDidDisappearCalled = false
    
    // MARK: - View Controller Lifecycle Methods
    override func viewDidLoad() {
        super.viewDidLoad()
        self.isViewDidLoadCalled = true
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.isViewWillAppearCalled = true
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.isViewDidAppearCalled = true
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.isViewWillDisappearCalled = true
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.isViewDidDisappearCalled = true
    }
}

```

次に、このView Controllerをテストするファイルを作成し、以下のようなセットアップを行います。

```swift:HogeViewControllerTests.swift

import XCTest
@testable import InvokeVCLifecycleMethods

class HogeViewControllerTests: XCTestCase {
    
    var hogeVC: HogeViewController!
    
    override func setUp() {
        super.setUp()
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        
        hogeVC = storyboard.instantiateInitialViewController() as! HogeViewController
    }
    
    override func tearDown() {
        super.tearDown()
    }

    func test_ViewDidLoad_Called() {
        XCTAssertTrue(hogeVC.isViewDidLoadCalled)
    }
    
    func test_ViewWillAndDidAppear_Called() {
        XCTAssertTrue(hogeVC.isViewWillAppearCalled)
        XCTAssertTrue(hogeVC.isViewDidAppearCalled)
    }
    
    func test_ViewWillAndDidDisappear_Called() {
        XCTAssertTrue(hogeVC.isViewWillDisappearCalled)
        XCTAssertTrue(hogeVC.isViewDidDisappearCalled)
    }
    
}

```

この状態でテストを実行すると、全てのテストがFailします。

<img width="919" alt="all_failed.png" src="https://qiita-image-store.s3.amazonaws.com/0/172234/0ff03deb-ce16-b734-b5b4-daf3bd83920b.png">

これで事前準備は完了です。

# View Controller LifeCycleメソッドを呼び出す
それでは早速、各種メソッドを呼び出していきましょう。

# ViewDidLoad()
~~ViewDidLoad()は、View Controllerの`view`プロパティにアクセスすることで呼び出せます。~~

[追記] @kazuhiro4949さんより、`view`へのアクセスではなく`loadViewIfNeeded()`で`ViewDidLoad()`を呼び出す方法を教えていただきました。ありがとうございます！

```swift
    func test_ViewDidLoad_Called() {
        // ViewDidLoad()を呼び出す
        hogeVC.loadViewIfNeeded()

        XCTAssertTrue(hogeVC.isViewDidLoadCalled)
    }
```

結果

<img width="616" alt="pass1.png" src="https://qiita-image-store.s3.amazonaws.com/0/172234/c82b4dd1-c27e-c87a-644e-e430e08df017.png">


Passしました。

# ViewWill(Did)Appear()
`ViewWillAppear()`は、UIViewControllerに[`beginAppearanceTransition(_:animated:)`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621387-beginappearancetransition)というインスタンスメソッドが用意されており、その第一引数(`_ isAppearing: Bool`)を`true`にして呼ぶことで呼び出せます。`ViewDidAppear()`は、そのインスタンスメソッドを呼んだ後に[`endAppearanceTransition()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621503-endappearancetransition)を呼ぶことで呼び出せます。

```swift
        // ViewWillAppear()を呼び出す(第一引数をtrueにする)
        hogeVC.beginAppearanceTransition(true, animated: false)
        // ViewDidAppear()を呼び出す
        hogeVC.endAppearanceTransition()
```

結果

<img width="773" alt="pass_appear.png" src="https://qiita-image-store.s3.amazonaws.com/0/172234/a978f0f5-7b5f-3af1-39d2-13f54e3283f7.png">

これもPassしました。

# ViewWill(Did)Disappear()
`ViewWillDisappear()`は、先程の[`beginAppearanceTransition(_:animated:)`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621387-beginappearancetransition)の第一引数(`_ isAppearing: Bool`)を`false`にして呼ぶことで呼び出せます。`ViewDidDisappear()`は、その後に[`endAppearanceTransition()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621503-endappearancetransition)を呼ぶことで呼び出せます。

```swift
    func test_ViewWillAndDidDisappear_Called() {
        // ViewWillDisappear()を呼び出す(第一引数をfalseにする)
        hogeVC.beginAppearanceTransition(false, animated: false)
        // ViewDidDisappear()を呼び出す
        hogeVC.endAppearanceTransition()
        
        XCTAssertTrue(hogeVC.isViewWillDisappearCalled)
        XCTAssertTrue(hogeVC.isViewDidDisappearCalled)
    }
```

結果

<img width="785" alt="pass_disappear.png" src="https://qiita-image-store.s3.amazonaws.com/0/172234/2fd86ab5-09ca-b95a-b47e-a7ffacc0d89b.png">

全てPassしました。

ユニットテストを書く時にご活用ください。

# 参考
Work with View Controllers
https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/WorkWithViewControllers.html)

[追記] loadViewIfNeeded()
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621446-loadviewifneeded

beginAppearanceTransition(_:animated:)
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621387-beginappearancetransition

endAppearanceTransition()
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621503-endappearancetransition

[iOS: The One Weird Trick For Testing View Controllers in Swift]
(https://www.natashatherobot.com/ios-testing-view-controllers-swift/)

[Testing in Swift](https://academy.realm.io/posts/testing-in-swift/)