MockSix 是一个微框架,旨在使 Swift 中的对象 Mock 更加容易。MockSix 构建于 Daniel Burbank 的 MockFive 之上。
如果您正在使用 Quick+Nimble,请务必查看 NimbleMockSix 中的 Nimble 匹配器扩展。
MockSix 通过接管一些样板代码并提供难以错误使用的 API,简化了手动对象 Mock。
示例代码
// original interface
protocol MyClassProtocol {
func myFunc(_ string: String) -> [Int]
}
// actual implementation
class MyClass: MyClassProtocol {
func myFunc(_ string: String) -> [Int] {
// ... whatever ...
return result
}
}
// mock implementation
class MockMyClass: MyClassProtocol, Mock {
enum Methods: Int {
case myFunc
}
typealias MockMethod = Methods
func myFunc(_ string: String) -> [Int] {
return registerInvocation(for: .myFunc,
args: string,
andReturn: [])
}
init() {}
}
// in the test case
let mock = MockMyClass()
mock.myFunc("foobar") == [] // true
mock.stub(.myFunc, andReturn: [42])
mock.myFunc("foobar") == [42] // true
mock.stub(.myFunc) { $0.isEmpty ? [] : [42] }
mock.myFunc("foobar") == [42] // true
构建要求:Swift 4.2
使用要求:macOS 10.10+,iOS 8.4+,tvOS 9.2+,Linux
通过 Cocoapods:将以下行添加到您的 Podfile
pod 'MockSix'
通过 Carthage:将以下行添加到您的 Cartfile(或 Cartfile.private)
github "lvsti/MockSix"
通过 Swift Package Manager:将其添加到您的 Package.swift 中的 dependencies
// swift-tools-version:4.0
let package = Package(
name: "MyAwesomeApp",
dependencies: [
.package(url: "https://github.com/lvsti/MockSix", from: "0.1.7"),
// ... other dependencies ...
],
targets: [
.target(name: "MyAwesomeApp", dependencies: []),
.testTarget(
name: "MyAwesomeAppTests",
dependencies: ["MyAwesomeApp", "MockSix"]
)
],
)
或者只需将 MockSix.swift
和 MockSixInternal.swift
添加到您的测试目标。
除了您要为其创建 Mock 的实际协议之外,还要遵循 Mock 协议
class MockFoobar: FoobarProtocol, Mock {
为您想要在 Mock 中提供的方法(“方法 ID”)声明一个枚举,并为其设置 MockMethod
类型别名
enum Methods: Int {
case doThis
case doThat
}
typealias MockMethod = Methods
该枚举必须具有 Int
类型的 RawValue
。
通过调用 registerInvocation
或 registerThrowingInvocation
来实现这些方法
func doThis(_ string: String, _ number: Int) -> [Int] {
return registerInvocation(for: .doThis,
args: string, number,
andReturn: [])
}
func doThat() throws -> Double {
return registerThrowingInvocation(for: .doThat,
andReturn: 0.0)
}
定义协议要求的任何属性
var stuff: Int = 0
}
在每个测试开始时调用 resetMockSix()
(通常在 beforeEach
代码块中)
像往常一样实例化和注入
let foobar = MockFoobar()
let sut = MyClass(foobar: foobar)
通过引用其方法 ID 来 stub 方法
// return value override
foobar.stub(.doThis, andReturn: [42])
// replace implementation with closure
foobar.stub(.doThis) { (args: [Any?]) in
let num = args[1]! as! Int
return [num]
}
foobar.stub(.doThat) { _ in
if arc4random() % 2 == 1 { throw FoobarError.unknown }
return 3.14
}
// invocation count aware stubbing
foobar.stub(.doThis, andReturn: [42], times: 1, afterwardsReturn: [43])
注意:返回值类型必须与函数的类型完全匹配,例如,要从具有 SomeClassProtocol
返回类型的函数返回符合 SomeClass
协议的实例,请使用显式类型转换
foobar.stub(.whatever, andReturn: SomeClass() as SomeClassProtocol)
移除 stubs 以恢复 Mock 实现中定义的行为
foobar.unstub(.doThat)
访问原始调用日志(如果确实需要;否则,最好使用 Nimble 匹配器)
// the mock has not been accessed
foobar.invocations.isEmpty
// doThis(_:_:) has been called twice
foobar.invocations
.filter { $0.methodID == MockFoobar.Methods.doThis.rawValue }
.count == 2
// doThis(_:_:) has been called with ("42", 42)
!foobar.invocations
.filter {
$0.methodID == MockFoobar.Methods.doThis.rawValue &&
$0.args[0]! as! String == "42" &&
$0.args[1]! as! Int == 42
}
.isEmpty
我还写了两篇关于 MockSix 的博文,可能有助于您入门
resetMockSix()
MockSix 在 MIT 许可证下发布。