Objective-Cではテストケース毎にオブジェクトの一部だけ挙動を変えたい場合に、OCMockなどのライブラリを使うのが普通でした。 それらのライブラリはNSInvocationやMethod Swizzlingなどを使ったいわゆる魔術的なコードで実現されていることが多く、 “テストコードを書いているのによくわからんコードが動いてる!”ってなってモヤモヤしたりします。

一方、Swiftではmanual mockingという手法が取られたりするみたいです。 例えば、

class Object {
    var foo: String {
        return "foo"
    }

    var bar: String {
        return "bar"
    }
}

のようなクラスに対して

class ObjectTests: XCTestCase {
    func testFoo() {
        class ObjectMock: Object {
            override var foo: String {
                return "hoge"
            }
        }
  
        let object = ObjectMock()
        XCTAssertEqual(object.foo, "hoge")
    }
}

という具合に、テストケース内にサブクラスを定義することでオブジェクトの挙動を変えることができます。 ObjectMockはテストケース内で定義されているので、他のテストケース内でもObjectMockという名前を再利用できます。

class ObjectTests: XCTestCase {
    func testFoo() {
        class ObjectMock: Object {
            override var foo: String {
                return "hoge"
            }
        }
  
        let object = ObjectMock()
        XCTAssertEqual(object.foo, "hoge")
    }

    func testBar() {
        class ObjectMock: Object {
            override var bar: String {
                return "fuga"
            }
        }
  
        let object = ObjectMock()
        XCTAssertEqual(object.bar, "fuga")
    }
}

“サブクラスくらいObjective-Cでも定義できるわ!”って思うかもしれないのですが、 Objective-Cではテストケース内にサブクラスを定義することができないので、以下のような問題が起きます。

  • モック用のサブクラスの定義がテストケースから遠い(コード上で)
  • 全てのサブクラスがグローバルな空間で定義されるので、一意な名前を考えるのがダルい

参考