Mirage
Mirage是一个为Swift项目提供的模拟库。推荐的生成模拟的方法是使用Fata Morgana以避免手动编写。
特性
使用Mirage可以做到以下事情:
- 创建模拟对象、存根、部分模拟对象
- 验证函数调用次数
- 获取调用参数历史记录
安装
需要Swift 4.2以上版本
Carthage
请将此行添加到Cartfile中,运行carthage update --platform iOS
并将二进制链接到目标,就像您通常做的那样)
github "valnoc/Mirage" ~> 2.0
Cocoapods
将该行添加到您的Podfile中的测试目标下,并运行pod update
。
pod 'Mirage', '~> 2.0'
Podfile示例
target 'MainTarget' do
...
target 'TestTarget' do
inherit! :search_paths
pod 'Mirage'
end
end
源文件
将“/Mirage”文件夹复制到您的测试目标中。
用法
详细内容请参考示例项目。尝试使用Fata Morgana生成mocks。
Mock
Mock是一种模仿真实对象行为的对象,并记录函数调用。您可以以相同的方式创建class
和protocol
mock。
Mirage的第一版提供了为整个类或协议创建mock的工具。mock可以轻松手动创建,但用法不太好 - 每次调用时您都必须将参数的类型转换为对应的类型,并且stubs返回的是
Any
。自从<强>Mirage 2<强>开始,函数是单独进行mock的。
所有的mocks和stubs都是泛型的。它们使用TArgs
和TReturn
类型。
如果函数有一个参数,则TArgs
应该是其类型。但如果它有几个参数,你应该创建一个结构体(或类)作为这些参数的容器。
TReturn
表示函数的返回类型。
Mock示例
让我们创建这个类的mock
class Calculator {
func sum(_ left: Int, _ right: Int) -> Int {
return left + right
}
}
完整版本
- 创建一个新的Mock类,继承自原始类
import Mirage
class MockCalculator: Calculator {
func sum
有两个参数left
和right
,所以我们创建一个嵌套类(或者是一个结构体)来包含它们
class SumArgs {
let left: Int
let right: Int
init(left: Int, right: Int) {
self.left = left
self.right = right
}
}
- 添加这个
func
以调用此函数的真实实现
fileprivate func super_sum(_ args: SumArgs) -> Int {
return super.sum(args.left, args.right)
}
- 添加
FuncCallHandler
。这是函数Mock的核心
lazy var mock_sum = FuncCallHandler<SumArgs, Int>(returnValue: anyInt(),
callRealFunc: { [weak self] (args) -> Int in
guard let __self = self else { return anyInt() }
return __self.super_sum(args)
})
覆盖
原始函数并调用mock_sum
以处理函数调用
override func sum(_ left: Int, _ right: Int) -> Int {
let args = SumArgs(left: left, right: right)
return mock_sum.handle(args)
}
这就完成了)
class MockCalculator: Calculator {
//MARK: - sum
class SumArgs {
let left: Int
let right: Int
init(left: Int, right: Int) {
self.left = left
self.right = right
}
}
fileprivate func super_sum(_ args: SumArgs) -> Int {
return super.sum(args.left, args.right)
}
lazy var mock_sum = FuncCallHandler<SumArgs, Int>(returnValue: anyInt(),
callRealFunc: { [weak self] (args) -> Int in
guard let __self = self else { return anyInt() }
return __self.super_sum(args)
})
override func sum(_ left: Int, _ right: Int) -> Int {
let args = SumArgs(left: left, right: right)
return mock_sum.handle(args)
}
}
简短版本
如果你不打算使用它,可以跳过 super_sum
和 callRealFunc
。你还可以使用一个结构体并得到一个生成的 init
。
class MockCalculator: Calculator {
//MARK: - sum
struct SumArgs {
let left: Int
let right: Int
}
lazy var mock_sum = FuncCallHandler<SumArgs, Int>(returnValue: anyInt())
override func sum(_ left: Int, _ right: Int) -> Int {
let args = SumArgs(left: left, right: right)
return mock_sum.handle(args)
}
}
存根
函数存根允许根据测试需求更改函数的行为。要创建存根,调用mock函数 whenCalled()
。
然后调用以下函数之一
thenReturn(_ result: TReturn)
以返回确切值作为结果thenDo(_ closure: @escaping Action)
以执行闭包而不是被调用函数thenCallRealFunc()
以调用此函数的真实实现
这些 thenSmth
调用可以链接起来,以对第一次调用返回一个结果,对后续调用返回另一个结果。
calculator.mock_sum.whenCalled().thenReturn(number)
randomNumberGenerator.mock_makeInt.whenCalled()
.thenReturn(5)
.thenReturn(10)
部分Mock
部分Mock与普通Mock相同,但它会自动调用其实际函数的实现。关于部分Mock是否是一个模式或反模式,是否有必要使用它,有一些讨论。
Mirage 允许你用一行代码创建一个部分Mock。至于是否使用它,由你自己决定。要创建一个函数的部分Mock,向 FuncCallHandler
中添加 isPartial: true
。
lazy var mock_performMainOperation = FuncCallHandler<Void, Void>(returnValue: (),
isPartial: true,
callRealFunc: { [weak self] (args) -> Void in
guard let __self = self else { return () }
return __self.super_performMainOperation()
})
验证调用次数
您可以在任何FuncCallHandler
上调用verify(called:)
来检查此函数被调用次数
- never: callTimes == 0
- once: callTimes == 1
- times: callTimes == *value*
- atLeast: callTimes >= *value*
- atMost: callTimes <= *value*
如果实际调用次数与给定的规则不符,verify(called:)
将抛出CallTimesRuleIsBroken
使用带verify
调用的XCTAssertNoThrow(try ...)
XCTAssertNoThrow(try randomNumberGenerator.mock_makeInt.verify(called: .times(2)))
XCTAssertNoThrow(try calculator.mock_sum.verify(called: .once))
XCTAssertNoThrow(try logger.mock_logPositiveResult.verify(called: .once))
XCTAssertNoThrow(try logger.mock_logNegativeResult.verify(called: .never))
调用参数
您可以使用args() -> TArgs?
或args(callTime: Int) -> TArgs?
函数从历史记录中获取任何调用的参数。它返回此调用的参数数组,或如果未注册给定调用时间的调用,则返回nil
因此,在预期这些参数存在的情况下,使用周围带有guard和XCTFail的argsOf()
是最好的做法。
// then
guard let args = calculator.mock_sum.args() else { XCTFail(); return }
XCTAssert(args.left == 5)
XCTAssert(args.right == 10)
迁移指南
为了将项目从 Mirage 框架的第一版本迁移到第二版本,该框架已被重命名为 Mirage2
。它允许在同一个项目中使用第一和第二个版本,并逐文件迁移。
Mirage从Mirage 1迁移到Mirage 2包含几个步骤。
- 重写模拟
使用 Fata Morgana 的新版本迁移模拟非常简单
-
使用查找和替换功能将
Once()
更改为.once
等 -
使用查找和替换功能与正则表达式一起将
verify
更改为when
调用。我稍后会给正则表达式 -
删除
args()
调用之后的参数转换
许可证
Mirage 在 MIT 许可证的范围内提供。