MockNRoll 0.0.3

MockNRoll 0.0.3

Daniel Saidi 维护。



MockNRoll 0.0.3

  • Daniel Saidi

Version CocoaPods Carthage Platform Swift 5.0 License Twitter: @danielsaidi

关于 Mock 'n' Roll

Mock 'n' Roll 是一个 Swift 的模拟库,它帮助您在单元测试时模拟功能。您可以使用完全支持参数调用的 invoke 方法调用,为任何函数 register 返回值,以及在您的模拟上进行 inspect 执行。

Mock 'n' Roll 支持模拟具有可选和非可选返回值的函数,以及无返回值的函数。它支持值、结构体、类和枚举,不对您编写的代码施加任何限制。

创建模拟

假设您有以下协议

protocol TestProtocol {
    
    func functionWithIntResult(arg1: String, arg2: Int) -> Int
    func functionWithStringResult(arg1: String, arg2: Int) -> String
    func functionWithStructResult(arg1: String, arg2: Int) -> User
    func functionWithClassResult(arg1: String, arg2: Int) -> Thing
    
    func functionWithOptionalIntResult(arg1: String, arg2: Int) -> Int?
    func functionWithOptionalStringResult(arg1: String, arg2: Int) -> String?
    func functionWithOptionalStructResult(arg1: String, arg2: Int) -> User?
    func functionWithOptionalClassResult(arg1: String, arg2: Int) -> Thing?
    
    func functionWithVoidResult(arg1: String, arg2: Int)
}

要模拟 TestProtocol,只需创建一个继承自 Mock 并实现 TestProtocol 的类

class TestClass: Mock, TestProtocol {
    
    func functionWithIntResult(arg1: String, arg2: Int) -> Int { ... }
    func functionWithStringResult(arg1: String, arg2: Int) -> String { ... }
    func functionWithStructResult(arg1: String, arg2: Int) -> User { ... }
    func functionWithClassResult(arg1: String, arg2: Int) -> Thing { ... }
    
    func functionWithOptionalIntResult(arg1: String, arg2: Int) -> Int? { ... }
    func functionWithOptionalStringResult(arg1: String, arg2: Int) -> String? { ... }
    func functionWithOptionalStructResult(arg1: String, arg2: Int) -> User? { ... }
    func functionWithOptionalClassResult(arg1: String, arg2: Int) -> Thing? { ... }
    
    func functionWithVoidResult(arg1: String, arg2: Int) {}

为了使用模拟类,您必须

  • 在每个函数中调用 invoke 以记录所有函数调用、它们的参数和返回值
  • 在您要测试的任何函数上,让您的测试 register 所需的任何返回值
  • 在您的测试中检查模拟的 executions 以断言测试是否成功执行

调用函数调用

每个模拟函数必须调用 invoke 以记录函数调用,包括输入参数和返回值。对于上面的 TestClass,看起来可能像这样

class TestClass: Mock, TestProtocol {
    
    func functionWithIntResult(arg1: String, arg2: Int) -> Int {
        return invoke(functionWithIntResult, args: (arg1, arg2))
    }
    
    func functionWithStringResult(arg1: String, arg2: Int) -> String {
        return invoke(functionWithStringResult, args: (arg1, arg2))
    }
    
    func functionWithStructResult(arg1: String, arg2: Int) -> User {
        return invoke(functionWithStructResult, args: (arg1, arg2))
    }
    
    func functionWithClassResult(arg1: String, arg2: Int) -> Thing {
        return invoke(functionWithClassResult, args: (arg1, arg2))
    }
    
    
    func functionWithOptionalIntResult(arg1: String, arg2: Int) -> Int? {
        return invoke(functionWithOptionalIntResult, args: (arg1, arg2))
    }
    
    func functionWithOptionalStringResult(arg1: String, arg2: Int) -> String? {
        return invoke(functionWithOptionalStringResult, args: (arg1, arg2))
    }
    
    func functionWithOptionalStructResult(arg1: String, arg2: Int) -> User? {
        return invoke(functionWithOptionalStructResult, args: (arg1, arg2))
    }
    
    func functionWithOptionalClassResult(arg1: String, arg2: Int) -> Thing? {
        return invoke(functionWithOptionalClassResult, args: (arg1, arg2))
    }
    
    
    func functionWithVoidResult(arg1: String, arg2: Int) {
        invoke(functionWithVoidResult, args: (arg1, arg2))
    }
}

无返回值的函数只需调用 invoke,而有返回值的函数必须调用 return invoke

每当您的单元测试接触这些函数中的任何一个时,现在您将能够检查记录的函数调用,以验证模拟是否按预期调用。

注册值

对于返回非可选值的函数,你的测试必须在触摸模拟函数之前注册实际返回值。未能这样做将使你的测试崩溃,并引发preconditionFailure

你可以通过调用模拟的registerResult(for:result:)函数来注册返回值,如下所示

let mock = TestClass()
mock.registerResult(for: mock.functionWithIntResult) { _ in return 123 }

由于结果块接受与实际函数相同的参数,因此您可以根据输入参数返回不同的结果值

let mock = TestClass()
mock.registerResult(for: mock.functionWithIntResult) { _, arg2 in  return arg2 }
mock.registerResult(for: mock.functionWithStringResult) { arg1, _ in  return arg1 }

对于返回void或可选值的函数,您可以不注册返回值,但只要您想影响测试,就应该注册。

断言模拟执行

为了验证模拟是否接收了预期的函数调用,您可以使用executions(for:)获取有关一个函数接收调用次数、输入参数和返回结果的信息

_ = mock.functionWithIntResult(arg1: "abc", arg2: 123)
_ = mock.functionWithIntResult(arg1: "abc", arg2: 456)
_ = mock.functionWithIntResult(arg1: "abc", arg2: 789)
_ = mock.functionWithStringResult(arg1: "abc", arg2: 123)
_ = mock.functionWithStringResult(arg1: "def", arg2: 123)

let intExecutions = mock.executions(of: mock.functionWithIntResult)
let stringExecutions = mock.executions(of: mock.functionWithStringResult)
expect(intExecutions.count).to(equal(3))
expect(stringExecutions.count).to(equal(2))
expect(intExecutions[0].arguments.0).to(equal("abc"))
expect(intExecutions[0].arguments.1).to(equal(123))
expect(intExecutions[1].arguments.0).to(equal("abc"))
expect(intExecutions[1].arguments.1).to(equal(456))
expect(intExecutions[2].arguments.0).to(equal("abc"))
expect(intExecutions[2].arguments.1).to(equal(789))
expect(stringExecutions[0].arguments.0).to(equal("abc"))
expect(stringExecutions[0].arguments.1).to(equal(123))
expect(stringExecutions[1].arguments.0).to(equal("def"))
expect(stringExecutions[1].arguments.1).to(equal(123))

注意,上面的代码使用了Quick/Nimble,以防您不熟悉该语法。

注册和引发错误

目前没有注册和引发错误的支持,这意味着异步函数(目前)无法注册自定义返回值。在此功能实施之前,您可以使用Mock类的error属性。

安装

CocoaPods

使用CocoaPods安装Mock 'n' Roll,请在您的Podfile文件中添加以下内容:

pod 'MockNRoll'

Carthage

使用Carthage安装Mock 'n' Roll,请在您的Cartfile文件中添加以下内容:

github "danielsaidi/MockNRoll"

手动安装

如果您不想使用Carthage或CocoaPods,可以将Mock 'n' Roll添加到您的应用中。首先克隆此存储库并将它放置在磁盘上的某个位置,然后将MockNRoll.xcodeproj添加到项目,并将MockNRoll.framework作为嵌入的应用程序二进制文件和目标依赖项添加。

重要的设备限制

Mock 'n' Roll使用不安全位转换来获取模拟函数的内存地址。这仅在64位设备上工作,这意味着基于模拟的单元测试在旧设备或模拟器(如iPad 2、iPad Retina等)上无法正确运行。

联系方式

希望您喜欢这个库。如果您有任何问题或想以任何方式做出贡献,请随时联系我。

鸣谢

Mock 'n' Roll受到Stubber的启发,但是拥有一个完全独立的代码库。虽然Stubber使用全局函数(这需要你时不时地清空执行存储),但Mock 'n' Roll将此逻辑移到每个模拟中,这意味着当模拟被销毁时,任何记录的执行都会自动清除。Mock 'n' Roll还增加了额外的功能,比如可选和空结果支持。

许可证

Mock 'n' Roll遵循MIT许可证。有关更多信息,请参阅LICENSE文件。