MockSix
MockSix 是一个微型框架,使 Swift 中的对象模拟更加容易。MockSix 是建立在 Daniel Burbank 的 MockFive 之上的。
如果您使用 Quick+Nimble,请确保您还查看 Nimble 匹配器扩展在 NimbleMockSix 上。
简介
MockSix 通过接管一些样板代码并提供一个不易用错的应用程序编程接口来简化手动对象模拟。
示例代码
// 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.swift中的依赖项
// 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
class MockFoobar: FoobarProtocol, Mock {
-
声明一个枚举,用于您想要在模拟中提供的函数("方法ID"),并将其设置为
MockMethod
别名enum Methods: Int { case doThis case doThat } typealias MockMethod = Methods
枚举必须具有
RawValue
的Int
类型。 -
通过通过调用
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来模拟方法
// 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)
-
删除模拟以恢复模拟实现中定义的行为
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的两篇博客,可能有助于您开始使用
- Swift中的轻量级对象模拟:动机和设计决策,以及MockSix Nimble匹配器的概述
- MockSix + Sourcery: Happily Ever After?:如何使用Sourcery自动生成MockSix样式的mock,而不需要编写任何样板代码
问题排除
- 调用的日志显示与主题无关的调用 => 尝试在测试用例的初始化阶段调用
resetMockSix()
- 测试因类型转换错误而崩溃 => 确保返回值的类型与模拟函数的返回类型相匹配;在需要时使用显式转换
许可证
MockSix根据MIT许可证发布。