【スナップショットテスト】mockを用いて動的な画面を生成する⭐️

## 概要
UnitTestを用いたスナップショットテストは色々なメリットがありますʕ•ᴥ•ʔノ🌟✨
例えば、

```
⭐️アプリ内のあらゆる画面のスクリーンショットを各種デバイスで自動で行う
　　　→デザイナーにスクショを共有する際の用意工数の削減
　　　→各種サイズの端末を用意しなくて済む
⭐️レイアウト差分を検出できる
　　　→意図しない変更を検知できる
```

などなど💏

その際に各画面の表示ロジックを実行することになりますが動的な画面を作る際が少々厄介だったりします😫
というのも、表示する際に通信を行いその結果で描画を行う画面の場合、
何にも考慮せずにそのまま画面を呼び出すと、、、、


#### 理想👍
<img width="300" alt="スクリーンショット 2019-12-17 0.45.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146629/9ac27e17-ea9b-d90f-b52c-85213a440ec3.png">

#### 現実🌀
<img width="300" alt="スクリーンショット 2019-12-17 0.25.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146629/824299ce-6859-7a24-5723-09fce079f0e2.png">


というように、必要な通信が行われず、
空っぽのViewControllerを取得する結果となってしまいます😱


ということで今回はスナップショットテストを導入する際などに使える、
動的な画面をMockを使用して生成できるようにする方法を紹介しようと思います＼＼\\٩( 'ω' )و //／／

**※スナップショット全体の説明については以下のスライドが参考になるので合わせて！**
**今回はスライドでは細かく触れられていないMock生成部分の具体例について書こうと思います。**
🌟[スナップショットテスト実戦投入 / Practical Snapshot Testing](https://speakerdeck.com/imaizume/practical-snapshot-testing)

## 前準備
### - 前提
* 導入したプロジェクトの設計はMVP+CleanArchitectureとしてます
    * この設計である必要はありませんが、DIという概念が必要となってきますʕ•ᴥ•ʔ以下の「設計」で後述します
* <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146629/e98ebed4-1863-90ac-45c7-76313911c689.png" width="300">


### - UseCaseMockの作成
* [Cuckoo](https://github.com/Brightify/Cuckoo)のインストール
    * →モックを自動生成してくれる&テストをサポートしてくれるフレームワーク
    * →[Cuckooを使ったiOSアプリのユニットテスト](https://qiita.com/Yaruki00/items/97f47cf71045c8e9bfe3)
* BuildPhraseに以下を記載し、ビルド時にUseCaseのMockを作成するshellを実行するようにする
<img width="523" alt="スクリーンショット 2019-12-17 3.17.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146629/59736f1c-f1fa-2015-2735-2b29c63cf3fb.png">


```ruby:プロジェクト/shell/generate_usecase_mocks.sh
set -eu

USE_CASE_OUTPUT_FILE = "$PROJECT_DIR/Mocks/GeneratedUseCaseMocks.swift"
USE_CASE_PROTOCOL_INPUT_DIR = "${PROJECT_DIR}/Domain/UseCase/Protocol"

"${PODS_ROOT}/Cuckoo/run" generate --testable "$PROJECT_NAME" --output "${USE_CASE_OUTPUT_FILE}" \
$USE_CASE_PROTOCOL_INPUT_DIR/*.swift
```

と、`USE_CASE_PROTOCOL_INPUT_DIR`階層内のメソッドが全てMock化されます＼＼\\٩( 'ω' )و //／／
USE_CASE_PROTOCOL_INPUT_DIR内には、PresenterからUseCaseに処理を委譲するためのProtocolファイル群が格納されています

```swift
protocol XxxUseCase: NSObjectProtocol {
    func getData()
...
}
```

↓ 自動生成される
<img width="752" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146629/6a9952d4-136a-d7f0-9576-c434bc2d896a.png">


## 画面の実装
```swift:ViewController
protocol XxxViewable: NSObjectProtocol {
    func xxx()
}

class XxxViewControllerFactory {
    static func create() -> XxxViewController {

        let vc = UIViewController.getViewControllerByStoryboard(storyboardName: "xxx",
                                                                            storyboardId: "xxx") as! XxxViewController
        viewController.presenter = PresenterBuilder().createXxxPresenter(view: viewController)
        return viewController
    }
}

class XxxViewController {
    fileprivate var presenter: XxxPresentable?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.presenter?.didLoad()
    }
}

extension XxxViewController: XxxViewable {
    func xxx() {
        print("")
    }
}
```

```swift:Presenter
protocol XxxPresentable: NSObjectProtocol {
    func didLoad()
}

class XxxPresenter: XxxPresentable {
    private weak var view: XxxViewable?
    private let xxxUseCase: XxxUseCase

    init(view: XxxViewable,
         xxxUseCase: XxxUseCase) {
        self.view = view
        self. xxxUseCase = xxxUseCase
    }

    func didLoad() {
        self.view?.xxx()
    }
```

上記のコードのポイントは、
VCが持つPresenterに`PresenterBuilder().createFixedPhrasePresenter(view: viewController)`を代入している点で、
これがいわゆる「依存性注入(DI)」で、今回のテーマを実現するために必要となってきます⭐️

というのも、
UnitTestでは実際の通信が行われないため、

```
普通にビルドする場合：実際に通信したデータを持つUseCaseを使用する
UnitTestでビルドする場合：Mockデータを持つUseCaseを使用する
```
必要があり、
そのためそれぞれのフェーズで向き先を変えて、presenterにinitされるuseCaseを切り替える必要があるからです🌟

（呼び出しクラスの切り替えについてはスナップショットテスト実行時に、
BuildPhrasesで`sed`を使用して文字列差し替えをする形などで実現すれば良いかなと思います）

```shell
sed -E 'PresenterBuilder/PresenterBuilderForAutoLayoutTests/'
```

## DIの実装
### 普通にビルドする場合のDI
→実際に通信したデータを持つUseCaseを使用する

```swift:PresenterBuilder
class PresenterBuilder {
    public func createXxxPresenter(view: XxxViewable) -> XxxPresentable {
        return XxxPresenter(view: view,
                            xxxUseCase: self.makeXxxUseCase())
    }

    private func makeXxxUseCase() -> XxxUseCase {
        return XxxUseCaseImpl(xxxRepository: self.xxxRepository()(略))
    }
}
```

### UnitTestでビルドする場合のDI
→Mockデータを持つUseCaseを使用する
前提で作成したusecaseのモックを使用します。

```swift:PresenterBuilderForAutoLayoutTests

import Cuckoo
import RxSwift

class PresenterBuilderForAutoLayoutTests {
    public func createXxxPresenter(view: XxxViewable) -> XxxPresentable {
        return XxxPresenter(view: view,
                            xxxUseCase: self.makeXxxUseCase())
    }

    private func makeXxxaUseCase() -> XxxUseCase {
        let mock = MockXxxUseCase()
        stub(mock) { stub in
            when(stub.getData()).then {
                return Single.create { subscriber in
                    subscriber(.error(NSError(domain: "", code: NSURLErrorUnknown, userInfo: nil)))
                    return Disposables.create()
                }
            }
        }
        return mock
    }
}
```

## テストの実行
最後にUnitTestを記述します。
メインキューで囲っているのは、FIFOでRxの処理(=VCの表示ロジック)が終わった後にVCをprintしたいためですʕ•ᴥ•ʔ

```swift
func testViewController() {
        let vc = XxxViewControllerFactory.create()
        DispatchQueue.main.async {
            print(vc)
        }
    }
}
```

Mock化するくだりの処理を行っていれば、以下の処理で、
理想👍が実現できているかと思います🎉🎉🎉🎉
<img width="300" alt="スクリーンショット 2019-12-17 0.45.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146629/9ac27e17-ea9b-d90f-b52c-85213a440ec3.png">

## 最後に
スナップショットテストと同時に実行できるため、
tarunonさんのAutoLayoutWarning検知のUnitTestを合わせて導入すると良いのかなと思ってます👍👍👍👍👍👍
https://github.com/tarunon/XCTAssertAutolayout