しおメモ

雑多な技術系ブログです。ニッチな内容が多いです。

NimbleのPredicateの自作方法とサンプル

Nimbleでテストを書いていた際に、共通部分をPredicateとして自作してまとめたいことがあったので、実装の方法と簡単なサンプルを記しておきます。

実装方法

github.com

Quick/NimbleのMatchersの中で、beEmptybeNilを見るとわかりやすいです。

matcherで使うために、Predicateを返すメソッドを定義する必要があります。
PredicatePredicate.simplePredicate.simpleNilableを使うと簡単に、生成することが出来ます。

func hoge<T>() -> Predicate<T> {
    return Predicate.simple("hoge") {
        // 判定処理
    }
}

PredicateStatusmatchesdoesNotMatchfailの3つからなるenumです。

説明
matches 判定条件に合致
doesNotMatch 判定条件に合致しない
fail 入力が上の2つのいずれの状態にもなりえない

.failは特殊ですが、ドキュメントコメントにあるように、nilexpectに入ったときなどに起こります。
expect(nil).to(equal(1))expect(nil).toNot(equal(1))はどちらもマッチ扱いにならずfailとなります。

サンプル

その1: ダウンキャストして比較

func beA<T, U: Equatable>(_ object: U) -> Predicate<T> {
    return Predicate.simple("be a") { actual in
        guard let actual = try actual.evaluate() as? U else { return .fail }

        return PredicateStatus(bool: actual == object)
    }
}

名前はCoreMatchersから拝借しました。

ダウンキャストに失敗した場合は.failとして、成功した場合は比較を行います。
条件式に対しては、PredicateStatus(bool: Bool)を使うことで簡単にPredicateStatusを作ることが出来ます。

その2: enumのassociated valueを比較

enum HogeCases {
    case hoge(count: Int)
    case fuga
}

func beHogeCountIs(_ expected: Int) -> Predicate<HogeCases> {
    return Predicate.simple("be hoge count is") { actual in
        guard let actual = try actual.evaluate() else { return .fail }
        guard case let .hoge(count) = actual else { return .doesNotMatch }

        return PredicateStatus(bool: count == expected)
    }
}

// 利用例
expect(HogeCases.hoge(count: 1)).to(beHogeCountIs(1))

enumのassociated valueを見る時のPredicateです。
.hogeでなかった場合は、toNotにしたときに引っかかってほしいため、doesNotMatchを返します。

実際のケースでもっと複雑な比較になっても、一度Predicateを作っておけば、複数のテストケースで利用でき、見通しが良くなります。