InstantMock 2.5.6

InstantMock 2.5.6

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最后发布2021年5月
SPM支持 SPM

Patrick Irlande 维护。



  • Patrick Irlande (pirishd)

InstantMock

在 Swift 中轻松创建 Mock

Build Status codecov.io CocoaPods Plattforms CocoaPods Version

InstantMock 致力于在 Swift 中轻松创建 Mock,并使用期望或存根实现来配置它们。

例如,请参阅 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 类继承。

// 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
    }

}

对于类

为了创建类的模拟,模拟必须采用 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 强类型,为了正常工作,模拟必须遵循一些关于返回值规则的约定。

可选返回值

语法如下

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

在这里,call() 返回 nil

非可选返回值

对于某些方法,模拟必须返回非可选值。如果返回值类型采用 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
}

属性

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

// 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()

如何模拟调用?

模拟的目的在于当函数被带有一些参数调用时执行某些操作。可以像以下例子那样设置

// 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)

返回值

在模拟实例上使用 andReturn(…) 设置返回值。

计算返回值

这是在模拟实例上使用 andReturn(closure: { _ in return … }) 实现的。这允许在同一个模拟中根据某些条件返回不同的值。

调用另一个函数

这是在模拟实例上使用 andDo { _ in … } 实现的。

抛出错误

这是在模拟实例上使用 andThrow(…) 实现的。

链式调用

在同一个桩上连续执行几个动作是可能的,前提是它们不相冲突。例如,可以返回一个值并调用另一个函数,如:andReturn(true).andDo { _ in print("something") }

规则

  • andDo 注册的最后一个闭包首先被调用
  • andThrow 注册的最后一个错误将被抛出
  • andReturn 注册的最后一个返回值将被返回
  • 否则,将调用注册的最后一个返回值计算方法,该方法通过 andReturn(closure:) 注册

重置桩

桩可以按这种方式重置

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")

参数匹配

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

确切值

可以使用 Arg.eq(…) 来进行确切值的匹配。

如果值符合以下条件,则可以匹配它们

  • 遵守 AnyObject 协议,所有类都隐式遵守此协议,例如 Arg.eq(NSString("hello"))
  • 遵守 MockUsable 协议,例如 Arg.eq(42)
  • 是类型,例如 Arg.eq(String.self)
  • 是元组,限于 5 个值,例如 Arg.eq(("a string", 42))

任何值

可以通过使用 Arg.any() 为采用 MockUsable 协议的类型匹配任意值。

特定条件

匹配验证特定条件的值使用 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

捕获闭包

捕获闭包特别有利于模拟方法的行为,带有回调函数,参见 此讨论

捕获闭包是一个特例。使用以下语法

限制:只要闭包的参数少于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 是一个协议,使类型在模拟中易于使用。对于一个给定的类型,它允许返回非可选值并匹配任何值。

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

  • 布尔型
  • 整型,Int64
  • 无符号整型,UInt64
  • 浮点型
  • 双精度浮点型
  • 字符串
  • 集合
  • 数组
  • 字典
  • 日期

更新日志

更改列表可在此处查看 here.

系统需求

  • Xcode 10
  • iOS 9
  • osX 10.10

安装

Cocoapods

InstantMock 可通过 CocoaPods 获取,见 Podfile 示例

target 'Example' do

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

end

Swift 包管理器

InstantMock 可通过 Swift 包管理器获取,可以通过 Xcode 或编辑 Package.swift 文件添加依赖。

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

灵感来源

作者

帕特里克·爱尔兰 - [email protected]

许可

InstantMock 认证授权于 MIT 许可证