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 |
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()
返回 nil
或 Void
。
对于某些方法,mock 必须返回非可选值。 如果返回值的类型采用了 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
}
可以 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()
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") }
。
规则
andDo
注册的最后一个闭包首先被调用andThrow
注册的最后一个错误被抛出andReturn
注册的最后一个返回值被返回andReturn(closure:)
注册的最后一个返回值计算方法可以使用以下方式重置 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(…)
完成匹配精确值。
如果值满足以下条件,则可以匹配它们
AnyObject
协议,所有类都隐式符合该协议,例如 Arg.eq(NSString("hello"))
MockUsable
协议,例如 Arg.eq(42)
Arg.eq(String.self)
Arg.eq(("a string", 42))
对于采用 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
是一种协议,使类型可以在 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
更改列表可以在这里找到。
InstantMock 可以使用 CocoaPods 获得,请参见 Podfile
示例
target 'Example' do
# Tests target
target 'ExampleTests' do
inherit! :search_paths
pod 'InstantMock'
end
end
InstantMock 可以使用 Swift Package Manager 获得,方法是使用 Xcode 或通过编辑 Package.swift
文件来添加依赖项
.package(url: "https://github.com/pirishd/InstantMock", from: "2.5.6"),
Patrick Irlande - pirishd@icloud.com
InstantMock 在 MIT 许可证下可用。