Mirage 2.1.0

Mirage 2.1.0

Valeriy Bezuglyy维护。



Mirage 2.1.0

  • Valeriy Bezuglyy

Mirage

License GitHub issues

Cocoapods release Carthage compatible GitHub release

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是一种模仿真实对象行为的对象,并记录函数调用。您可以以相同的方式创建classprotocolmock。

Mirage的第一版提供了为整个类或协议创建mock的工具。mock可以轻松手动创建,但用法不太好 - 每次调用时您都必须将参数的类型转换为对应的类型,并且stubs返回的是Any。自从<强>Mirage 2<强>开始,函数是单独进行mock的。

所有的mocks和stubs都是泛型的。它们使用TArgsTReturn类型。

如果函数有一个参数,则TArgs应该是其类型。但如果它有几个参数,你应该创建一个结构体(或类)作为这些参数的容器。

TReturn表示函数的返回类型。

Mock示例

让我们创建这个类的mock

class Calculator {
    func sum(_ left: Int, _ right: Int) -> Int {
        return left + right
    }
}
完整版本
  1. 创建一个新的Mock类,继承自原始类
import Mirage

class MockCalculator: Calculator {
  1. func sum 有两个参数 leftright,所以我们创建一个嵌套类(或者是一个结构体)来包含它们
    class SumArgs {
        let left: Int
        let right: Int
        
        init(left: Int, right: Int) {
            self.left = left
            self.right = right
        }
    }
  1. 添加这个 func 以调用此函数的真实实现
    fileprivate func super_sum(_ args: SumArgs) -> Int {
        return super.sum(args.left, args.right)
    }
  1. 添加 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)
    })
  1. 覆盖原始函数并调用 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_sumcallRealFunc。你还可以使用一个结构体并得到一个生成的 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包含几个步骤。

  1. 重写模拟

使用 Fata Morgana 的新版本迁移模拟非常简单

  1. 使用查找和替换功能将 Once() 更改为 .once

  2. 使用查找和替换功能与正则表达式一起将 verify 更改为 when 调用。我稍后会给正则表达式

  3. 删除 args() 调用之后的参数转换

许可证

Mirage 在 MIT 许可证的范围内提供。