我最近读到了如何通过创建一个协议并用默认实现扩展该协议来向swift中的结构/类添加“traits/mixins”。这很好,因为它允许我添加查看控制器的功能,而不必向视图控制器添加一组助手对象。我的问题是,如何存根这些默认实现提供的调用?
下面是一个简单的例子:

protocol CodeCop {
  func shouldAllowExecution() -> Bool
}

extension CodeCop {
  func shouldAllowExecution() -> Bool {
    return arc4random_uniform(2) == 0
  }
}

struct Worker : CodeCop {
  func doSomeStuff() -> String {
    if shouldAllowExecution() {
       return "Cop allowed it"
     } else {
       return "Cop said no"
    }
  }
}

如果我想写两个测试,一个验证字符串“cop allowed it”在codecop不允许执行时由dostuf()返回,另一个验证字符串“cop said no”在codecop不允许执行时由dostuf()返回。

最佳答案

这很简单,只需在测试目标中编写一个名为CodeCopStub的附加协议即可,该协议继承自CodeCop

protocol CodeCopStub: CodeCop {
    // CodeCopStub declares a static value on the implementing type
    // that you can use to control what is returned by
    // `shouldAllowExecution()`.
    //
    // Note that this has to be static, because you can't add stored instance
    // variables in extensions.
    static var allowed: Bool { get }
}

然后扩展继承自CodeCopStubshouldAllowExecution()方法,根据新静态变量返回值。这将覆盖实现CodeCop的任何类型的原始allowed实现。
extension CodeCopStub {
    func shouldAllowExecution() -> Bool {
        // We use `Self` here to refer to the implementing type (`Worker` in
        // this case).
        return Self.allowed
    }
}

此时,您所要做的就是使CodeCop符合CodeCopStub
extension Worker: CodeCopStub {
    // It doesn't matter what the initial value of this variable is, because
    // you're going to set it in every test, but it has to have one because
    // it's static.
    static var allowed: Bool = false
}

然后,您的测试将如下所示:
func testAllowed() {
    // Create the worker.
    let worker = Worker()
    // Because `Worker` has been extended to conform to `CodeCopStub`, it will
    // have this static property. Set it to true to cause
    // `shouldAllowExecution()` to return `true`.
    Worker.allowed = true

    // Call the method and get the result.
    let actualResult = worker.doSomeStuff()
    // Make sure the result was correct.
    let expectedResult = "Cop allowed it"
    XCTAssertEqual(expectedResult, actualResult)
}

func testNotAllowed() {
    // Same stuff as last time...
    let worker = Worker()
    // ...but you tell it not to allow it.
    Worker.allowed = false

    let actualResult = worker.doSomeStuff()
    // This time, the expected result is different.
    let expectedResult = "Cop said no"
    XCTAssertEqual(expectedResult, actualResult)
}

记住,所有这些代码都应该放在测试目标中,而不是主要目标中。把它放在测试目标中,不会影响原始代码,也不需要修改原始代码。

07-27 13:58