64
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swiftのモック生成ライブラリ「Mockolo」のセットアップ&操作方法

Last updated at Posted at 2020-01-03

はじめに

Xcodeにはモック生成機能が搭載されておらず、手動で実装するのが大変だと感じてきたため、導入することにしました。

「Mockolo」とは?

Swift用のモック生成ライブラリです。

現在はプロトコルのモック生成のみ対応しており、クラスのモック生成は追加予定とのことです。
1.1.3でクラスのモック生成もサポートされました。
https://github.com/uber/mockolo/releases/tag/1.1.3

環境

  • OS:macOS Catalina 10.15.2
  • Swift:5.1.3
  • Xcode:11.3 (11C29)
  • Mockolo:1.1.1

セットアップ

Mockoloのインストール

Mintからインストールします。

Mintfile
+ uber/mockolo@1.1.1
$ mint bootstrap

手動でインストールするには、公式ドキュメントをご参照ください。
https://github.com/uber/mockolo#build--install

ビルド時にモックを生成するようにする

Mockoloはモックの生成時間が速いため、ビルドするたびにモックを生成するようにします。
ビルド時間が気になる場合、手動でコマンドを実行してモックを生成してください。

Xcodeでプロジェクトを開く
TARGETSで製品ターゲットを選択 > Build Phases > +をクリック > New Run Script Phase >
ドラッグ&ドロップで「Compile Sources」の直前に移動

スクリーンショット_2020-01-03_16_43_59.jpg

スクリプトは「Generate Mocks with Mockolo」のようにわかりやすい名前を付けるといいです。

展開して以下のスクリプトを記述します。

if which mint >/dev/null; then
  rm -f $SRCROOT/MockResults.swift
  mint run mockolo mockolo --sourcedirs $SRCROOT/{製品ターゲット名} --destination $SRCROOT/MockResults.swift
else
  echo "warning: Mint not installed, download from https://github.com/yonaskolb/Mint"
fi

すでにモックがあるとビルドエラーになることがあるため、生成前に rm で削除しています。

Output Files > +をクリック
--destination で指定しているファイルパスを記述します。

$SRCROOT/MockResults.swift

生成されるファイルを記述しないと、CI/CD時に以下のエラーが発生します。

error: Build input file cannot be found:

以下の記事を参考にさせていただきました。
https://qiita.com/lovee/items/fa3ef5e60cfbf31996c0

スクリーンショット_2020-01-04_12_16_45.jpg

Mintを使っていない場合、 mint run mockolo を外し、if文の条件を変更してください。

使っているオプションを説明します。
以下の2つは必須であり、必要に応じて値を変更してください。

オプション 説明
--sourcedirs 生成対象のフォルダパス
製品ターゲット名のフォルダを指定すれば、通常は全ファイルを対象にできる
--destination モックの生成パス

--mock-final は任意ですが、私は付けるのが好みです。

オプション 説明
--mock-final モックに final を付ける
1.2.8で追加された

その他のオプションは公式ページまたは mockolo --help をご参照ください。

プロジェクトをビルドし、「$SRCROOT(通常はプロジェクトのルートフォルダ)」に「MockResults.swift」が生成されたら、プロジェクトにドラッグ&ドロップします。
スクリーンショット_2020-01-03_16_53_20.jpg

[Copy items if needed]チェックをOFFにし、[Finish]をクリックします。
スクリーンショット_2020-01-03_16_51_53.jpg

バージョン管理から無視する

不要な競合を防ぐため、生成された「MockResults.swift」をバージョン管理の対象外にします。

Gitを使っている場合、以下を「.gitignore」に追加するのみでOKです。

.gitignore
+ MockResults.swift

操作方法

モックを生成したいプロトコルに @mockable のドキュメンテーションコメントを付けます。
タイプエイリアスがある場合、カッコ内に書きます。

Foo.swift
/// @mockable(typealias: T = AnyObject; U = StringProtocol)
public protocol Foo {
    associatedtype T
    associatedtype U: Collection where U.Element == T 
    associatedtype W 

    var num: Int { get set }

    func bar(arg: Float) -> String
}

ビルドすると、モックが生成されます。

MockResults.swift
// クラス名は `{プロトコル名}Mock` となる
public class FooMock: Foo {
    typealias T = AnyObject
    typealias U = StringProtocol
    typealias W = Any // 指定しないと `Any` になる

    init() {}
    init(num: Int = 0) {
        self.num = num
    }

    var numSetCallCount = 0
    var underlyingNum: Int = 0
    var num: Int {
        get {
            return underlyingNum
        }
        set {
            underlyingNum = newValue
            numSetCallCount += 1
        }
    }

    var barCallCount = 0
    var barHandler: ((Float) -> (String))?
    func bar(arg: Float) -> String {
        barCallCount += 1
        if let barHandler = barHandler {
            return barHandler(arg)
        }
        return ""
    }
}

自動生成されたコードのうち、テストで使うプロパティのみ説明します。

プロパティ 説明
{プロパティ名}SetCallCount セッターの呼び出し回数
{メソッド名}CallCount メソッドの呼び出し回数
{メソッド名}Handler メソッドの呼び出し時に実行されるクロージャ

テスト時は以下のように使います。

FooTests.swift
func testMock() {
    // モックを生成する
    let mock = FooMock(num: 5)

    // 対象プロパティのセット回数を確認する
    XCTAssertEqual(mock.numSetCallCount, 1)

    // ハンドラは対象メソッドの呼び出し前に自分で代入する
    mock.barHandler = { arg in
        return String(arg)
    }

    // 対象メソッドの呼び出し回数を確認する
    XCTAssertEqual(mock.barCallCount, 0)
}

おわりに

とても簡単にモックを生成できました!
これでテスト時にVIPERのモックを手動で実装する手間が省けるぞ😊

もっと早く導入すればよかったと思いました笑

参考リンク

64
30
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?