SwiftyMock 0.2.3

SwiftyMock 0.2.3

测试测试情况
语言语言 SwiftSwift
许可证 MIT
发布最新发布2018 年 7 月
SPM支持 SPM

Paul TaykaloAlex Voronov 维护。



  • By
  • Stanfy

SwiftyMock 构建状态

此仓库包含辅助程序,使在 Swift 中进行模拟、存根和监视变得更加容易

示例

协议

想象您有一个协议描述了一些行为

protocol RoboKitten {
    func batteryStatus() -> Int
    func jump(x x: Int, y: Int) -> Int
    func canJumpAt(x x: Int, y: Int) -> Bool
    func rest(completed: Bool -> () )
}

协议使用

这个协议被用在了某处

class RoboKittenController {
    let kitten: RoboKitten

    init(kitten: RoboKitten) {
        self.kitten = kitten
    }
    
    func jumpAt(x x: Int, y: Int) -> Result {
        if kitten.canJumpAt(x: x, y: y) {
            kitten.jump(x: x, y: y)
            return .SUCCESS
        }
        return .FAILURE
    }
    ...
}

协议模拟

那么现在您想要测试该协议作为依赖项的使用情况
并创建协议的模拟实现
并为要测试的每个方法创建假的调用
那么在SwiftyMock中,它会看起来怎么样

class RoboKittenMock: RoboKitten {

    let batteryStatusCall = FunctionCall<(), Int>()
    func batteryStatus() -> Int {
        return stubCall(batteryStatusCall, argument:())
    }

    let jumpCall = FunctionCall<(x: Int, y: Int), Int>()
    func jump(x x: Int, y: Int) -> Int {
        return stubCall(jumpCall, argument: (x: x, y: y))
    }
    
    let canJumpAtCall = FunctionCall<(x: Int, y: Int), Bool>()
    func canJumpAt(x x: Int, y: Int) -> Bool {
        return stubCall(canJumpAtCall, argument: (x: x, y: y))
    }

    let restCall = FunctionCall<Bool -> (), ()>()
    func rest(completed: Bool -> ()) {
        return stubCall(restCall, argument: completed, defaultValue: ())
    }
}

模拟使用方法

如果设置正确,通过使用替换方法指定模拟行为非常简单

方法存根

假设我们希望模拟返回某些值
这非常简单

// like this
kittenMock.canJumpAtCall.returns(false)

// or like this
kittenMock.jumpCall.returns(20)

有时,您有更复杂的规则,确定何时以及返回什么

// You can add as many filters you like
// More specific rules overrides general rules
kittenMock.canJumpAtCall
    .on { $0.x < 0 }.returns(false)
    .on { $0.y < 0 }.returns(false)
    .returns(true) // in all other cases
    

有时您可能需要更复杂的模拟行为,例如,需要将闭包传递到模拟中

protocol RoboKitten {
    func rest(completed: Bool -> () )
}

kittenMock.restCall.performs { completion in
    print("Mock method was called! Remove this in prod version:))")
    completion(true)
}

方法调用检查

有时候,您需要确保方法被调用

beforeEach {
    // Since canjump method need to return somtehing we need to specify return value
    kittenMock.canJumpAtCall.returns(false)
}
it("should ask kitten if it's available to jump there") {
    sut.jumpAt(x: 10, y: 20)
    expect(kittenMock.canJumpAtCall.called).to(beTruthy())
}

或者您需要检查方法是否被精确地调用了一定次数

it("should actually ask kitten to jump only once per call") {
    sut.jumpAt(x: 18, y: 23)
    expect(kittenMock.jumpCall.callsCount).to(equal(1))
    
    sut.jumpAt(x: 80, y: 15)
    expect(kittenMock.jumpCall.callsCount).to(equal(2))
}

所有方法调用都存储在模拟中,因此您可以轻松地检查模拟是否使用了正确的参数

it("should ask kitten if it's available to jump there with the same coords") {
    sut.jumpAt(x: 10, y: 20)
    expect(kittenMock.canJumpAtCall.capturedArgument?.x).to(equal(10))
    expect(kittenMock.canJumpAtCall.capturedArgument?.y).to(equal(20))
}

ReactiveSwift存根支持

返回 SignalProducerAction 方法的API与普通方法存根几乎相同。

设想RoboKitten协议略有变化,返回SignalProducer而不是纯值,这样我们可以关注电池电量

protocol RoboKitten {
    func batteryStatus() -> SignalProducer<Int, NoError>
}

现在,您创建这个协议的模拟实现,但是您使用的是ReactiveCall而不是FunctionCall。唯一的不同之处在于我们添加了第三个类型约束来指定Error

class RoboKittenMock: RoboKitten {
    let batteryStatusCall = ReactiveCall<(), Int, NoError>()
    func batteryStatus() -> SignalProducer<Int, NoError> {
        return stubCall(batteryStatusCall, argument:())
    }
}

方法占位符

由于SignalProducer受限于ValueError类型,我们需要允许用户选择哪个来模拟方法调用。幸运的是,有方便的Result类型及其实现,它们与ReactiveSwift一起提供。因此,为了模拟ReactiveCall,你使用Result而不是之前使用FunctionCall时的普通值。

// like this
kittenMock.batteryStatusCall.returns(.success(42))
// or
kittenMock.batteryStatusCall.returns(Result(value: 42))

// or in case you want this stub to return failure
kittenMock.batteryStatusCall.returns(.failure(ImagineThisIsError))
// or
kittenMock.batteryStatusCall.returns(Result(error: ImagineThisIsError))

其他一切都保持不变 :)

匹配器

SwiftyMock没有自己的匹配器,因此您可以使用最适合您的任何匹配器 :)

模板

您可以使用Sourcery自动生成模拟。
首先,创建Sourcery配置yml,并指定源文件、模板、生成的输出路径以及测试框架的测试路径。您可以看一下根目录下的.sourcery.yml是如何配置的。

sources: 
  - ./Example/SwiftyMock/RoboKitten
templates: 
  - ./SwiftyMock/Templates
output:
  path: ./Example/RoboKittenTests/Mocks/Generated
args:
  testable: SwiftyMock_Example # here you specify your application module name, that you're importing for testing

其次,用// sourcery: Mock注释标注您想为它们生成模拟的协议。

// sourcery: Mock
protocol RoboKitten {
    // ...
}

第三,运行Sourcery命令sourcery --config .sourcery.yml --watch,如果您想运行一个服务,每次您的源文件或模板发生变化时都会重新生成模拟。
或者,如果您只想生成一次模拟,则使用sourcery --config .sourcery.yml