MockSix 0.2.0

MockSix 0.2.0

测试已测试
语言语言 SwiftSwift
许可证 NOASSERTION
发布最新版本2019年8月
SPM支持 SPM

Tamas Lustyik维护。



MockSix 0.2.0

MockSix

Carthage compatible Swift Package Manager compatible CocoaPods compatible Swift 4.2 platforms

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.swiftMockSixInternal.swift添加到您的测试目标中。

用法

创建模拟实现
  1. 除您正在创建模拟的实际协议外,遵守Mock

    class MockFoobar: FoobarProtocol, Mock {
  2. 声明一个枚举,用于您想要在模拟中提供的函数("方法ID"),并将其设置为MockMethod别名

        enum Methods: Int {
            case doThis
            case doThat
        }    
        typealias MockMethod = Methods

    枚举必须具有RawValueInt类型。

  3. 通过通过调用registerInvocationregisterThrowingInvocation来实现方法

        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)
        }
  4. 定义协议要求的任何属性

        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的两篇博客,可能有助于您开始使用

问题排除

  • 调用的日志显示与主题无关的调用 => 尝试在测试用例的初始化阶段调用 resetMockSix()
  • 测试因类型转换错误而崩溃 => 确保返回值的类型与模拟函数的返回类型相匹配;在需要时使用显式转换

许可证

MockSix根据MIT许可证发布。