InstantMock
在 Swift 中轻松创建 Mock
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 协议(这对于大多数常用类型如 Bool
、Int
等来说是适用的),只需要像以下示例中将结果强制展开到 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 协议(这对于大多数常用类型如 Bool
、Int
等来说是适用的),只需要像以下示例中将结果强制展开到 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 许可证。