InstantMock

在 Swift 中轻松创建 Mock

Build Status codecov.io CocoaPods Plattforms CocoaPods Version

InstantMock 旨在轻松地在 Swift 中创建 mock,并使用期望或 stubbed 实现来配置它们。

有关示例,请参见 Example.playground

Swift 版本兼容性

Swift 版本 InstantMock 版本
5.0 2.5.X
4.2 2.2.X
4.0 2.0/2.1
3.X 1.1.X

如何创建 Mock?

InstantMock 允许创建可在多个测试中使用的单个 mock,用于协议或类。

对于协议

为协议创建 mock 的最简单方法是从 Mock 类继承。

// MARK: Protocol to be mocked
protocol Foo {
    func bar(arg1: String, arg2: Int) -> Bool
}

// MARK: Mock class inherits from `Mock` and adopts the `Foo` protocol
class FooMock: Mock, Foo {

    // implement `bar` of the `Foo` protocol
    func bar(arg1: String, arg2: Int) -> Bool {
        return super.call(arg1, arg2)! // provide values to parent class
    }

}

对于类

要为类创建 mock,mock 必须采用 MockDelegate 协议。

// MARK: Class to be mocked
class Foo {
    func bar(arg1: String, arg2: Int) -> Bool
}

// MARK: Mock class inherits from `Foo` and adopts the `MockDelegate` protocol
class FooMock: Foo, MockDelegate {

    // create `Mock` delegate instance
    private let mock = Mock()

    // conform to the `MockDelegate` protocol, by providing the `Mock` instance
    var it: Mock {
        return mock
    }

    // implement `bar` of the `Foo` class
    override func bar(arg1: String, arg2: Int) -> Bool {
        return mock.call(arg1, arg2)! // provide values to the delegate
    }

}

规则

为了正常工作,由于 Swift 的强类型,mock 必须遵守一些关于返回值的规则。

可选返回值

语法如下

func returnsOptional() -> Bool? {
    return mock.call()
}

在这里,call() 返回 nilVoid

非可选返回值

对于某些方法,mock 必须返回非可选值。 如果返回值的类型采用了 MockUsable 协议(对于像 BoolInt 等最常见的类型来说就是这种情况),只需强制解包 call() 的结果,如下例所示

func returnsMockUsable() -> Bool { // `Bool` adopts `MockUsable`
    return mock.call()! // force unwrapping
}

对于其他类型,请确保提供默认值,如下例所示

func returnsCustom() -> CustomType {
    return mock.call() ?? CustomType() // return a `CustomType` default value
}

抛出异常

要捕获抛出异常的方法上的错误,只需使用 callThrowing() 代替 call()

如果返回值的类型采用了 MockUsable 协议(对于像 BoolInt 等最常见的类型来说就是这种情况),只需强制解包 callThrowing() 的结果,如下例所示

func bazMockUsable() throws -> Bool {
    return try callThrowing()!
}

对于其他类型,请确保提供默认值,如下例所示

func bazCustom() throws -> CustomType {
    return try callThrowing() ?? CustomType() // return a `CustomType` default value
}

属性

可以 mock 协议中声明的属性,如下例所示

// define protocol with a property `prop` that has a getter and a setter
protocol FooProperty {
    var prop: String { get set }
}

// mock of `FooProperty`
class FooPropertyMock: Mock, FooProperty {
    var prop: String {
        get { return super.call()! }
        set { return super.call(newValue) }
    }
}

如何设置期望?

期望旨在验证是否使用某些参数进行了调用。 它们是使用如下语法设置的

// create mock instance
let mock = FooMock()

// create expectation on `mock`, that is verified when `bar` is called
// with "hello" for `arg1` and any value of the type of `arg2`
mock.expect().call(
    mock.bar(arg1: Arg.eq("hello"), arg2: Arg.any())
)

拒绝

拒绝与期望相反。 它们确保没有使用某些参数进行调用。 只需使用 reject() 代替 expect()

调用次数

此外,可以在调用次数上设置期望和拒绝:使用以下语法

// create expectation on `mock`, that is verified when 2 calls are done on `bar`
// with "hello" for `arg1` and any value of the type of `arg2`
mock.expect().call(
    mock.bar(arg1: Arg.eq("hello"), arg2: Arg.any()),
    count: 2
)

属性

可以使用以下语法在属性上设置期望

// create mock instance
let mock = FooPropertyMock()

// create expectation on `mock`, that is verified when the property `prop` is called
mock.expect().call(mock.prop)

// create expectation on `mock`, that is verified when the property `prop` is set
// with the exact value "hello"
mock.expect().call(
    mock.property.set(mock.prop, value: Arg.eq("hello"))
)

验证

验证期望和拒绝是这样完成的

// test fails when any of the expectations or rejections set on `mock` is not verified
mock.verify()

重置期望

可以使用以下方式重置期望

mock.resetExpectations()

如何 Stub 调用?

Stubs 旨在在调用带有某些参数的函数时执行操作。 它们是使用如下语法设置的

// create mock instance
let mock = FooMock()

// create stubbed implementation of the `bar` method, which returns `true` when called
// with "hello" for `arg1` and any value of the type of `arg2`
mock.stub().call(
    mock.bar(arg1: Arg.eq("hello"), arg2: Arg.any())
).andReturn(true)

返回值

使用 stub 实例上的 andReturn(…) 设置返回值。

计算返回值

这是使用 stub 实例上的 andReturn(closure: { _ in return … }) 完成的。这使得可以在同一 stub 上返回不同的值,具体取决于某些条件。

调用另一个函数

这是使用 stub 实例上的 andDo { _ in … } 完成的。

抛出错误

这是使用 stub 实例上的 andThrow(…) 完成的。

链式调用

可以在同一 stub 上链接多个操作,前提是它们不冲突。 例如,可以返回值并调用另一个函数,例如 andReturn(true).andDo { _ in print("something") }

规则

重置 Stubs

可以使用以下方式重置 Stubs

mock.resetStubs()

例子

// configure mock to return "string" when calling `basic` whatever provided arguments
mock.stub().call(mock.basic(arg1: Arg.any(), arg2: Arg.any())).andReturn("string")

// reset the previously configured stubs
mock.resetStubs()

// calling `basic` does not return "string"
let ret = mock.basic(arg1: "", arg2: 2)
XCTAssertNotEqual(ret, "string")

参数匹配

只有当参数与注册的内容匹配时,才会验证期望。调用 stubbed 实现也是如此。

精确值

使用 Arg.eq(…) 完成匹配精确值。

如果值满足以下条件,则可以匹配它们

任意值

对于采用 MockUsable 协议的类型,可以使用 Arg.any() 完成匹配任何值。

特定条件

使用 Arg.verify({ _ in return … }) 完成匹配验证特定条件的值。

闭包

匹配闭包是一种特殊情况。使用以下语法:Arg.closure()

限制:只要闭包的参数少于 5 个,就可以匹配它们。

参数捕获

由于 ArgumentCaptor 类,参数也可以被捕获以供以后使用。

例如

// create captor for type `String`
let captor = ArgumentCaptor<String>()

// create expectation on `mock`, that is verified when `bar` is called
// with 42 for `arg2`. All values for `arg1` are captured.
mock.expect().call(mock.bar(arg1: captor.capture(), arg2: Arg.eq(42)))
...

// retrieve the last captured value
let value = captor.value

// retrieve all captured values
let values = captor.allValues

捕获闭包

捕获闭包对于 stubbing 带有回调的方法的行为特别有用,请参阅这个对话

捕获闭包是一种特殊情况。 使用以下语法

限制:只要闭包的参数少于 5 个,就可以捕获它们。

// create captor for type closure `(Int) -> Bool`
let captor = ArgumentClosureCaptor<(Int) -> Bool>()
...
// retrieve the last captured closure, and call it
let ret = captor.value!(42)

MockUsable

MockUsable 是一种协议,使类型可以在 mocks 中轻松使用。 对于给定的类型,它允许返回非可选值并匹配任何值。

通过创建一个采用该协议的扩展,可以在现有类型上添加 MockUsable 。 例如

extension SomeClass: MockUsable {

    static var any = SomeClass() // any value

    // return any value
    public static var anyValue: MockUsable {
        return SomeClass.any
    }

    // returns true if an object is equal to another `MockUsable` object
    public func equal(to value: MockUsable?) -> Bool {
        guard let value = value as? SomeClass else { return false }
        return self == value
    }

}

在使用继承的现有类型上添加 MockUsable 时,应始终在最深层的子类上完成。 实际上,将此扩展添加到父类和子类都会造成构建冲突。

支持的类型

目前,以下类型是 MockUsable

更新日志

更改列表可以在这里找到。

要求

安装

Cocoapods

InstantMock 可以使用 CocoaPods 获得,请参见 Podfile 示例

target 'Example' do

    # Tests target
    target 'ExampleTests' do
        inherit! :search_paths
        pod 'InstantMock'
    end

end

Swift Package Manager

InstantMock 可以使用 Swift Package Manager 获得,方法是使用 Xcode 或通过编辑 Package.swift 文件来添加依赖项

.package(url: "https://github.com/pirishd/InstantMock", from: "2.5.6"),

灵感

作者

Patrick Irlande - pirishd@icloud.com

许可

InstantMockMIT 许可证下可用。