1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MockoloをSPMで導入する

Posted at

Mockolo とは

Mockoloの説明は公式READMEに頼ります。

Mockolo is an efficient mock generator for Swift. Swift doesn't provide mocking support, and Mockolo provides a fast and easy way to autogenerate mock objects that can be tested in your code. One of the main objectives of Mockolo is fast performance. Unlike other frameworks, Mockolo provides highly performant and scalable generation of mocks via a lightweight commandline tool, so it can run as part of a linter or a build if one chooses to do so.

日本語訳

Mockolo は、Swift 用の効率的なモック ジェネレーターです。Swift はモックをサポートしていませんが、Mockolo はコード内でテストできるモック オブジェクトを高速かつ簡単に自動生成する方法を提供します。Mockolo の主な目的の 1 つは、高速なパフォーマンスです。他のフレームワークとは異なり、Mockolo は軽量のコマンドライン ツールを介して、非常に高性能でスケーラブルなモック生成を提供するため、必要に応じてリンターまたはビルドの一部として実行できます。

通信を行う処理などは、単体テストなどではそのまま使うと結果を制御するのが難しいので、大体はMockクラスを作って戻り値を制御したりすることでテストの安定性などを担保しつつ通信などが絡む振る舞いのテストを行います。
ただMockクラスをいちいち作るのは大変という問題もあります。(XcodeはMockの生成をサポートしていないので)
MockoloではMockクラスの自動生成を行ってくれるため、テストを行うためのMock作成のコストを大幅に削減できるようになります。

SPM で導入するモチベーション

そもそも、Swift のライブラリ管理ツールは SPM の他にも CocoaPods や Carthage、ツール系で言えば、Mint や Bundler などたくさんあります。
管理ツールによっては対応していないライブラリなどもありますが、SPMの場合は、Apple 純正のツールということもあり、最近だと SPM で導入できるライブラリはかなり増えています。(タイトルの通りMockoloもSPMに対応しています!)
ライブラリ管理ツールはできれば統一できたほうが、ライブラリの管理も楽になるので
であれば、近年でデファクトスタンダードになりつつある SPM で導入した方が、ライブラリ管理ツールの統一に一歩近づくという判断でSPMによる導入を検討してみました。

SPM での導入手順

公式の README に基本的な導入方法は記載していたので、これを参考に導入していきます。

手順 1. Package.swift を作成する

Mockolo を使う対象が SPM の Package.swift で管理されている場合は、この手順で新しく Package.swift を作らずとも、もともとある Package.swift に Mockolo を追加できるので必要ない

Xcode -> File -> New -> Packageから Package.swift のプロジェクトを作成します。

作成された Pakcage.swift の例が以下です。

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "MockoloPluginLib",
    products: [
        // Products define the executables and libraries a package produces, making them visible to other packages.
        .library(
            name: "MockoloPluginLib",
            targets: ["MockoloPluginLib"]),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .target(
            name: "MockoloPluginLib"),
        .testTarget(
            name: "MockoloPluginLibTests",
            dependencies: ["MockoloPluginLib"]
        ),
    ]
)

手順 2. Package.swift に Mockolo の依存を追加する

dependenciesmockoloを追加します。
バージョンは 2025/3/22 時点の最新バージョンである2.3.1を指定します。

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "MockoloPluginLib",
    products: [
        .library(
            name: "MockoloPluginLib",
            targets: ["MockoloPluginLib"]),
    ],
+   dependencies: [
+       .package(url: "https://github.com/uber/mockolo", exact: "2.3.1")
+   ],
    targets: [
        .target(
            name: "MockoloPluginLib"),
        .testTarget(
            name: "MockoloPluginLibTests",
            dependencies: ["MockoloPluginLib"]
        ),
    ]
)

手順 3. ターゲットに Plugin を追加

targetに Mockolo 実行用の plugin を定義します。
また必要ないターゲットを全て削除します(今回必要なのは Mockolo を実行するための plugin のみのため)

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "MockoloPluginLib",
    products: [
-       .library(
-           name: "MockoloPluginLib",
-           targets: ["MockoloPluginLib"]),
-           products: [
+       .plugin(name: "RunMockolo", targets: ["RunMockolo"])
    ],
    ],
    dependencies: [
        .package(url: "https://github.com/uber/mockolo", exact: "2.3.1")
    ],
    targets: [
-       .target(
-           name: "MockoloPluginLib"),
-       .testTarget(
-           name: "MockoloPluginLibTests",
-           dependencies: ["MockoloPluginLib"]
-       ),
+       .plugin(
+           name: "RunMockolo",
+           capability: .buildTool(),
+           dependencies: [.target(name: "mockolo")]
+       ),
+       .binaryTarget(
+           name: "mockolo",
+           url: "https://github.com/uber/mockolo/releases/download/2.3.1/mockolo-macos.artifactbundle.zip",
+           checksum: "daefaebdb24bf0f0a5f830523330b81850e9e95a3e2fc2011a50054309a55eae"
+       ),
   ]
)

binaryTargetに指定した値は、README でも書いてあるが、リリースノートに記載されているものを設定しました。

.binaryTarget(
    name: "mockolo",
    url: "https://github.com/uber/mockolo/releases/download/2.3.1/mockolo-macos.artifactbundle.zip",
    checksum: "daefaebdb24bf0f0a5f830523330b81850e9e95a3e2fc2011a50054309a55eae"
),

ついでにここで、先ほど削除したターゲットのフォルダは全て削除しておきます。
SourcesTestsのフォルダをそのまま削除します(以下キャプチャの赤枠箇所)
image.png

手順 4. plugin の実装を追加する

Pluginsディレクトリを作成し、その配下にRunMockoloディレクトリを作成します。
以下のようなディレクトリ構成になるはずです。(Plugins配下のディレクトリ名は Package.swift で定義した plugin のターゲット名が入るので適宜変更すること)
image.png

RunMockoloに以下内容のファイルを作成します。

import Foundation
import PackagePlugin

@main
struct RunMockoloPlugin: BuildToolPlugin {
	func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {

		let engine = try RunMockoloEngine(pluginWorkDirectory: context.pluginWorkDirectoryURL,
		                                  targetUrl: URL(string: target.directory.string)!,
		                                  mockoloPath: context.tool(named: "mockolo").url)
		return engine.build()
	}
}

#if canImport(XcodeProjectPlugin)
	import XcodeProjectPlugin

	extension RunMockoloPlugin: XcodeBuildToolPlugin {

		func createBuildCommands(context: XcodeProjectPlugin.XcodePluginContext,
		                         target: XcodeProjectPlugin
		                         	.XcodeTarget) throws -> [PackagePlugin.Command] {

			let engine = try RunMockoloEngine(pluginWorkDirectory: context.pluginWorkDirectoryURL,
			                                  targetUrl: context.xcodeProject.directoryURL,
			                                  mockoloPath: context.tool(named: "mockolo").url)
			return engine.build()
		}
	}

#endif

struct RunMockoloEngine {

	let pluginWorkDirectory: URL
	let targetUrl: URL
	let mockoloPath: URL

	func build() -> [Command] {

		let generatedSourcePath = pluginWorkDirectory.appending(component: "GeneratedMocks.swift")

		return [.prebuildCommand(displayName: "Run mockolo",
		                         executable: mockoloPath,
		                         arguments: ["-s", targetUrl.path(),
		                                     "-d", generatedSourcePath.path()],
		                         outputFilesDirectory: pluginWorkDirectory)]
	}
}

やっていることは、plugin を使用するターゲット内でビルドしたときに、モックを生成するだけです。

XcodeBuildToolPluginに準拠した拡張を実装した理由は、project ファイルのビルドフェーズのRun Build Tool Plug-insにで使用できるようにするためです。
XcodeBuildToolPluginに準拠していないと、Plugin doesn't support Xcode projectsのエラーが発生してしまいます。

image.png

ここまでの手順で、一旦 Mockolo を使うための Plugin を作成する作業は終了です。

project ファイルで Mockolo を使う

以下のように、package dependencies から上記手順で作成したローカルの SPM モジュール(Package.swift が入っているディレクトリ)を指定します。

image.png

あとは、アプリケーションターゲットのビルドフェーズの設定を開いて、Run Build Tool Plug-insRunMockoloを指定するだけです。
image.png

これができたらアプリケーションターゲットのビルドを行うと、/// @mockableがついたprotocolのモックが自動生成されるようになります。

導入後の詳しい使い方は以下記事がとてもわかりやすかったです。

SPM モジュール内で Mockolo を使う

Package.swift の target 定義で、以下のように plugin を指定すれば、ビルド時にモックを自動生成してくれるようになります。

targets: [
        ...
        .target(
            name: "RxSwiftExample",
            dependencies: [
                ...
            ],
            plugins: [
                .plugin(name: "RunMockolo")
            ]
        ),
        ...
    ]

SPM で Mockolo を使用している Package.swift の例は以下の通り。

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "RxSwiftExample",
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "RxSwiftExample",
            targets: ["RxSwiftExample"]),
    ],
    dependencies: [
        .package(url: "https://github.com/ReactiveX/RxSwift.git", exact: "6.9.0"),
        .package(url: "https://github.com/uber/mockolo", exact: "2.3.1")
    ],
    targets: [
        .plugin(
            name: "RunMockolo",
            capability: .buildTool(),
            dependencies: [.target(name: "mockolo")]
        ),
        .binaryTarget(
            name: "mockolo",
            url: "https://github.com/uber/mockolo/releases/download/2.3.1/mockolo-macos.artifactbundle.zip",
            checksum: "daefaebdb24bf0f0a5f830523330b81850e9e95a3e2fc2011a50054309a55eae"
        ),
        .target(
            name: "RxSwiftExample",
            dependencies: [
                "RxSwift",
                .product(name: "RxCocoa", package: "RxSwift")
            ],
            plugins: [
                .plugin(name: "RunMockolo")
            ]
        ),
        .testTarget(
            name: "RxSwiftExampleTests",
            dependencies: ["RxSwiftExample"]
        ),
    ]
)

SPM モジュール内で Mockolo の Plugin を使っているリポジトリは以下になります。

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?