你怎么在Swift中写一个模拟器?

时间:2017-11-15 05:05:49

标签: ios swift unit-testing mocking

我在哪里放置模拟代码?我需要再写一遍吗?我是否更改了原始代码?

1 个答案:

答案 0 :(得分:2)

简短回答:使用协议。

如果您的injectable对象是final,struct或enum,则甚至无法将其覆盖为mock。而不是使用具体类型作为您的依赖项,使用协议并使您的实现符合它。除了允许“模拟”而不管实际类型(类,结构,枚举)之外,它还在一个地方列出了公共接口,不受实现的干扰。它还会迫使您考虑需要成为非私有界面的一部分。

使用追溯协议一致性(即在扩展中),您甚至可以使用它来模拟系统类,例如CBCentralManagerCLLocationManager

示例:

不容易模仿:

struct Foo {
    let id: Int
    let data: Int
}

final class FooManager {
    var x: Int

    func getFoo(id: Int) -> Foo {
        return Foo(id: id, data: x)
    }
}

class FooUser {
    let fooManager: FooManager
    init(fooManager: FooManager) {
        self.fooManager = fooManager
    }
    func getData() -> Int {
        return fooManager.getFoo(id: 3).data
    }
}

琐碎的模仿:

struct Foo {
    let id: Int
    let data: Int
}

// Easy to see what's visible.
protocol FooManager {
    func getFoo(id: Int) -> Foo
}

final class RealFooManager: FooManager {
    private var x: Int

    func getFoo(id: Int) -> Foo {
        return Foo(id: id, data: x)
    }
}

class FooUser {
    let fooManager: FooManager
    init(fooManager: FooManager) {
        self.fooManager = fooManager
    }
    func getData() -> Int {
        return fooManager.getFoo(id: 3).data
    }
}

// In test target.
class MockFooManager: FooManager {
    var requestedId: Int?
    var data: Int = 17
    func getFoo(id: Int) -> Foo {
        requestedId = id
        return Foo(id, data: data)
    }
}

class FooUserTests {
    func testFooUserGetData() {
        let mock = MockFooManager()
        let user = FooUser(fooManager: mock)
        let data = user.getData()
        XCTAssertEqual(data, mock.data)
        XCTAssertEqual(mock.requestedId, 3)
    }
}