我最近读到了如何通过创建一个协议并用默认实现扩展该协议来向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 }
}
然后扩展继承自
CodeCopStub
的shouldAllowExecution()
方法,根据新静态变量返回值。这将覆盖实现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)
}
记住,所有这些代码都应该放在测试目标中,而不是主要目标中。把它放在测试目标中,不会影响原始代码,也不需要修改原始代码。